@sellable/mcp 0.1.27 → 0.1.28
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 +22 -4
- package/dist/server.js +6 -0
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +20 -3
- package/dist/tools/bootstrap.d.ts +1 -1
- package/dist/tools/bootstrap.js +19 -0
- package/dist/tools/leads.js +11 -3
- package/dist/update-check.d.ts +25 -0
- package/dist/update-check.js +210 -0
- package/package.json +1 -1
- package/skills/create-campaign-v2/SKILL.md +5 -0
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ command.
|
|
|
33
33
|
For Claude Code and Codex CLI:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npx -y @sellable/install --host all --token skt_live_your_token_here --workspace-id your_workspace_id
|
|
36
|
+
npx -y @sellable/install@latest --host all --token skt_live_your_token_here --workspace-id your_workspace_id
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Agent-readable install instructions are available at:
|
|
@@ -52,6 +52,24 @@ sh /tmp/sellable-install.sh
|
|
|
52
52
|
Phase 112 v1 uses package stdio MCP through `@sellable/mcp`. Hosted HTTP
|
|
53
53
|
MCP is a future/advanced mode until the hosted endpoint exists.
|
|
54
54
|
|
|
55
|
+
Package-mode installs launch `@sellable/mcp@latest`, so each fresh host MCP
|
|
56
|
+
start resolves the newest stable npm release. The MCP server also runs a cached
|
|
57
|
+
startup/auth update check and tells the agent to run
|
|
58
|
+
`npx -y @sellable/install@latest` when the installed runtime is behind npm.
|
|
59
|
+
|
|
60
|
+
### Publishing `@sellable/mcp`
|
|
61
|
+
|
|
62
|
+
Publish from the repo root with:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run mcp:publish
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The script loads `.env` and `prod.env`, builds `mcp/sellable`, verifies npm auth,
|
|
69
|
+
and publishes with a temporary npm config file. Prefer `NPM_TOKEN=<npm token>` in
|
|
70
|
+
`prod.env`; `NODE_AUTH_TOKEN`, `SELLABLE_NPM_TOKEN`, and `NPM_PUBLISH_TOKEN` are
|
|
71
|
+
also supported.
|
|
72
|
+
|
|
55
73
|
### 2. Generate API Token
|
|
56
74
|
|
|
57
75
|
1. Go to https://app.sellable.dev/settings
|
|
@@ -63,7 +81,7 @@ MCP is a future/advanced mode until the hosted endpoint exists.
|
|
|
63
81
|
Install MCP server:
|
|
64
82
|
|
|
65
83
|
```bash
|
|
66
|
-
claude mcp add --transport stdio sellable -- npx -y @sellable/mcp
|
|
84
|
+
claude mcp add --transport stdio sellable -- npx -y @sellable/mcp@latest
|
|
67
85
|
```
|
|
68
86
|
|
|
69
87
|
Create auth config at `~/.sellable/config.json`:
|
|
@@ -83,14 +101,14 @@ The token is provided when you generate it. Use `list_workspaces` +
|
|
|
83
101
|
For customer/package installs, use the public installer:
|
|
84
102
|
|
|
85
103
|
```bash
|
|
86
|
-
npx -y @sellable/install@
|
|
104
|
+
npx -y @sellable/install@latest --host codex --token skt_live_your_token_here --workspace-id your_workspace_id
|
|
87
105
|
```
|
|
88
106
|
|
|
89
107
|
If you already have `~/.sellable/config.json`, rerun/verify without rewriting
|
|
90
108
|
auth:
|
|
91
109
|
|
|
92
110
|
```bash
|
|
93
|
-
npx -y @sellable/install@
|
|
111
|
+
npx -y @sellable/install@latest --host codex
|
|
94
112
|
sellable --verify-only --host codex
|
|
95
113
|
```
|
|
96
114
|
|
package/dist/server.js
CHANGED
|
@@ -29,6 +29,7 @@ import { attachRecommendedSequence, attachSequence, createWorkflowTable, sequenc
|
|
|
29
29
|
import { listTables, tableToolDefinitions } from "./tools/tables.js";
|
|
30
30
|
import { handleVerifyTableRow, verifyRowToolDefinitions, } from "./tools/verify-row.js";
|
|
31
31
|
import { addTeammate, createWorkspace, getActiveWorkspace, listWorkspaces, setActiveWorkspace, workspaceToolDefinitions, } from "./tools/workspaces.js";
|
|
32
|
+
import { checkForUpdates, logUpdateNotice } from "./update-check.js";
|
|
32
33
|
const server = new Server({
|
|
33
34
|
name: "sellable-mcp",
|
|
34
35
|
version: "1.0.0",
|
|
@@ -515,6 +516,11 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
515
516
|
async function main() {
|
|
516
517
|
const startTime = Date.now();
|
|
517
518
|
console.error(`[sellable-mcp] pid=${process.pid} starting...`);
|
|
519
|
+
void checkForUpdates()
|
|
520
|
+
.then(logUpdateNotice)
|
|
521
|
+
.catch(() => {
|
|
522
|
+
// Update checks are advisory and must never block MCP startup.
|
|
523
|
+
});
|
|
518
524
|
const transport = new StdioServerTransport();
|
|
519
525
|
// Exit cleanly when parent disconnects stdin (Claude Code closed/reconnected)
|
|
520
526
|
process.stdin.on("end", () => {
|
package/dist/tools/auth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SellableUpdateStatus } from "../update-check.js";
|
|
1
2
|
export type AuthStatus = {
|
|
2
3
|
ok: boolean;
|
|
3
4
|
configPath: string;
|
|
@@ -10,6 +11,7 @@ export type AuthStatus = {
|
|
|
10
11
|
checkedAt: string;
|
|
11
12
|
/** Short message the LLM MUST show the user after auth succeeds. */
|
|
12
13
|
_userNotice: string | null;
|
|
14
|
+
update: SellableUpdateStatus | null;
|
|
13
15
|
error: null | {
|
|
14
16
|
type: "config" | "auth" | "workspace" | "api";
|
|
15
17
|
status?: number;
|
package/dist/tools/auth.js
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
import { getApi, SellableApiError } from "../api.js";
|
|
2
2
|
import { getConfig, getConfigPath } from "../auth.js";
|
|
3
|
+
import { checkForUpdates } from "../update-check.js";
|
|
3
4
|
function maskToken(token) {
|
|
4
5
|
if (token.length <= 10)
|
|
5
6
|
return `${token.slice(0, 4)}...`;
|
|
6
7
|
return `${token.slice(0, 8)}...${token.slice(-2)}`;
|
|
7
8
|
}
|
|
9
|
+
async function getUpdateStatus() {
|
|
10
|
+
try {
|
|
11
|
+
return await checkForUpdates();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function appendUpdateNotice(notice, update) {
|
|
18
|
+
if (!update?._userNotice)
|
|
19
|
+
return notice;
|
|
20
|
+
return notice ? `${notice}\n${update._userNotice}` : update._userNotice;
|
|
21
|
+
}
|
|
8
22
|
export const authToolDefinitions = [
|
|
9
23
|
{
|
|
10
24
|
name: "get_auth_status",
|
|
11
|
-
description: "Verify Sellable authentication early. Call this before any campaign work. Returns ok=false with clear guidance when the token is missing, revoked, or the active workspace is not set. IMPORTANT: When auth succeeds, ALWAYS tell the user the _userNotice message so they know which workspace is active.",
|
|
25
|
+
description: "Verify Sellable authentication early. Call this before any campaign work. Also performs a cached Sellable package update check. Returns ok=false with clear guidance when the token is missing, revoked, or the active workspace is not set. IMPORTANT: When auth succeeds, ALWAYS tell the user the _userNotice message so they know which workspace is active and whether an update is available.",
|
|
12
26
|
inputSchema: {
|
|
13
27
|
type: "object",
|
|
14
28
|
properties: {},
|
|
@@ -20,6 +34,7 @@ export const authToolDefinitions = [
|
|
|
20
34
|
export async function getAuthStatus() {
|
|
21
35
|
const configPath = getConfigPath();
|
|
22
36
|
const checkedAt = new Date().toISOString();
|
|
37
|
+
const update = await getUpdateStatus();
|
|
23
38
|
const base = {
|
|
24
39
|
ok: false,
|
|
25
40
|
configPath,
|
|
@@ -29,7 +44,8 @@ export async function getAuthStatus() {
|
|
|
29
44
|
activeWorkspaceName: null,
|
|
30
45
|
tokenPrefix: null,
|
|
31
46
|
workspacesCount: null,
|
|
32
|
-
_userNotice: null,
|
|
47
|
+
_userNotice: appendUpdateNotice(null, update),
|
|
48
|
+
update,
|
|
33
49
|
checkedAt,
|
|
34
50
|
};
|
|
35
51
|
try {
|
|
@@ -87,7 +103,8 @@ export async function getAuthStatus() {
|
|
|
87
103
|
activeWorkspaceName: workspaceName,
|
|
88
104
|
tokenPrefix: maskToken(config.token),
|
|
89
105
|
workspacesCount: workspaces.length,
|
|
90
|
-
_userNotice: notice,
|
|
106
|
+
_userNotice: appendUpdateNotice(notice, update),
|
|
107
|
+
update,
|
|
91
108
|
checkedAt,
|
|
92
109
|
error: null,
|
|
93
110
|
};
|
|
@@ -9,7 +9,7 @@ type BootstrapCreateCampaignInput = {
|
|
|
9
9
|
refreshContext?: boolean;
|
|
10
10
|
};
|
|
11
11
|
type BootstrapCheck = {
|
|
12
|
-
key: "auth" | "framework" | "resume_context" | "subskill_catalog" | "create_campaign_subskill";
|
|
12
|
+
key: "auth" | "update" | "framework" | "resume_context" | "subskill_catalog" | "create_campaign_subskill";
|
|
13
13
|
ok: boolean;
|
|
14
14
|
blocking: boolean;
|
|
15
15
|
detail: string;
|
package/dist/tools/bootstrap.js
CHANGED
|
@@ -10,6 +10,9 @@ function toGuidance(check, message) {
|
|
|
10
10
|
if (check === "auth") {
|
|
11
11
|
return "Run get_auth_status and fix auth/workspace setup before continuing.";
|
|
12
12
|
}
|
|
13
|
+
if (check === "update") {
|
|
14
|
+
return "Run npx -y @sellable/install@latest, then restart Claude Code/Codex so Sellable reloads.";
|
|
15
|
+
}
|
|
13
16
|
if (check === "framework") {
|
|
14
17
|
return "Fix framework files and rerun bootstrap_create_campaign.";
|
|
15
18
|
}
|
|
@@ -101,6 +104,22 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
101
104
|
if (!ok) {
|
|
102
105
|
pushBlockingError(blockingErrors, "auth", authCheck.status.error?.message || "Authentication check failed.");
|
|
103
106
|
}
|
|
107
|
+
const update = authCheck.status.update;
|
|
108
|
+
if (update && !update.disabled) {
|
|
109
|
+
requiredChecks.push({
|
|
110
|
+
key: "update",
|
|
111
|
+
ok: !update.blocking,
|
|
112
|
+
blocking: update.blocking,
|
|
113
|
+
detail: update.updateAvailable
|
|
114
|
+
? update._userNotice || "A newer Sellable MCP package is available."
|
|
115
|
+
: update.mcp.latestVersion
|
|
116
|
+
? `Sellable MCP is current (${update.mcp.currentVersion ?? "unknown"}).`
|
|
117
|
+
: "Sellable MCP update check completed without latest-version data.",
|
|
118
|
+
});
|
|
119
|
+
if (update.blocking) {
|
|
120
|
+
pushBlockingError(blockingErrors, "update", update._userNotice || "Sellable MCP update required.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
124
|
catch (error) {
|
|
106
125
|
const message = toErrorMessage(error);
|
package/dist/tools/leads.js
CHANGED
|
@@ -2205,10 +2205,18 @@ export async function confirmLeadList(input) {
|
|
|
2205
2205
|
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2206
2206
|
});
|
|
2207
2207
|
const campaignTableId = importResult.workflowTableId ?? importResult.campaignTableId;
|
|
2208
|
-
if
|
|
2208
|
+
// Persist currentStep if the caller asked for it. Do NOT touch
|
|
2209
|
+
// selectedLeadListId here: the campaign table id is already saved to
|
|
2210
|
+
// CampaignOffer.workflowTableId by /api/v3/campaign-builder/import-leads,
|
|
2211
|
+
// and selectedLeadListId must remain pointing at the source lead list
|
|
2212
|
+
// (config.type === "lead_list") so the UI's Continue handler — which feeds
|
|
2213
|
+
// selectedLeadListId back into /api/v3/campaign-builder/import-leads as
|
|
2214
|
+
// sourceLeadListId — can find a valid lead list. Overwriting it with the
|
|
2215
|
+
// campaign table id makes that endpoint 404 with "Lead list not found"
|
|
2216
|
+
// and silently breaks the Leads → Filter Continue button.
|
|
2217
|
+
if (shouldSetCurrentStep) {
|
|
2209
2218
|
await api.put(`/api/v2/campaign-offers/${campaignOfferId}`, {
|
|
2210
|
-
|
|
2211
|
-
...(shouldSetCurrentStep ? { currentStep: effectiveCurrentStep } : {}),
|
|
2219
|
+
currentStep: effectiveCurrentStep,
|
|
2212
2220
|
});
|
|
2213
2221
|
}
|
|
2214
2222
|
return {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type PackageUpdateStatus = {
|
|
2
|
+
packageName: string;
|
|
3
|
+
currentVersion: string | null;
|
|
4
|
+
latestVersion: string | null;
|
|
5
|
+
outdated: boolean;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
export type SellableUpdateStatus = {
|
|
9
|
+
checkedAt: string;
|
|
10
|
+
cachePath: string;
|
|
11
|
+
fromCache: boolean;
|
|
12
|
+
disabled: boolean;
|
|
13
|
+
mcp: PackageUpdateStatus;
|
|
14
|
+
installer: PackageUpdateStatus;
|
|
15
|
+
updateAvailable: boolean;
|
|
16
|
+
blocking: boolean;
|
|
17
|
+
guidance: string | null;
|
|
18
|
+
_userNotice: string | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function checkForUpdates(options?: {
|
|
21
|
+
force?: boolean;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
ttlMs?: number;
|
|
24
|
+
}): Promise<SellableUpdateStatus>;
|
|
25
|
+
export declare function logUpdateNotice(status: SellableUpdateStatus): void;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as os from "os";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
const MCP_PACKAGE = "@sellable/mcp";
|
|
5
|
+
const INSTALL_PACKAGE = "@sellable/install";
|
|
6
|
+
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 1500;
|
|
8
|
+
function cachePath() {
|
|
9
|
+
return path.join(os.homedir(), ".sellable", "update-check.json");
|
|
10
|
+
}
|
|
11
|
+
function readCurrentVersion() {
|
|
12
|
+
const entrypointDir = process.argv[1]
|
|
13
|
+
? path.dirname(path.resolve(process.argv[1]))
|
|
14
|
+
: process.cwd();
|
|
15
|
+
const candidates = [
|
|
16
|
+
path.resolve(entrypointDir, "..", "package.json"),
|
|
17
|
+
path.resolve(process.cwd(), "mcp/sellable/package.json"),
|
|
18
|
+
];
|
|
19
|
+
for (const pkgPath of candidates) {
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
22
|
+
if (pkg.name === MCP_PACKAGE && typeof pkg.version === "string") {
|
|
23
|
+
return pkg.version;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Keep looking; version detection is advisory only.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
function registryPackagePath(packageName) {
|
|
33
|
+
return encodeURIComponent(packageName);
|
|
34
|
+
}
|
|
35
|
+
async function fetchLatestVersion(packageName, timeoutMs) {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(`https://registry.npmjs.org/${registryPackagePath(packageName)}/latest`, {
|
|
40
|
+
signal: controller.signal,
|
|
41
|
+
headers: {
|
|
42
|
+
Accept: "application/json",
|
|
43
|
+
"User-Agent": "sellable-mcp-update-check",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
const body = (await response.json());
|
|
50
|
+
if (typeof body.version !== "string" || !body.version) {
|
|
51
|
+
throw new Error("npm registry response did not include a version");
|
|
52
|
+
}
|
|
53
|
+
return body.version;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function parseVersion(version) {
|
|
60
|
+
if (!version)
|
|
61
|
+
return null;
|
|
62
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
63
|
+
if (!match)
|
|
64
|
+
return null;
|
|
65
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
66
|
+
}
|
|
67
|
+
function isVersionOutdated(currentVersion, latestVersion) {
|
|
68
|
+
const current = parseVersion(currentVersion);
|
|
69
|
+
const latest = parseVersion(latestVersion);
|
|
70
|
+
if (!current || !latest)
|
|
71
|
+
return false;
|
|
72
|
+
for (let i = 0; i < 3; i += 1) {
|
|
73
|
+
if (current[i] < latest[i])
|
|
74
|
+
return true;
|
|
75
|
+
if (current[i] > latest[i])
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function readCache(ttlMs) {
|
|
81
|
+
const file = cachePath();
|
|
82
|
+
try {
|
|
83
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
84
|
+
if (!raw.checkedAtMs || Date.now() - raw.checkedAtMs > ttlMs)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
...raw,
|
|
88
|
+
cachePath: file,
|
|
89
|
+
fromCache: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function writeCache(status) {
|
|
97
|
+
const file = cachePath();
|
|
98
|
+
try {
|
|
99
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
100
|
+
const { fromCache: _fromCache, ...cacheableStatus } = status;
|
|
101
|
+
const cache = {
|
|
102
|
+
...cacheableStatus,
|
|
103
|
+
cachePath: file,
|
|
104
|
+
checkedAtMs: Date.now(),
|
|
105
|
+
};
|
|
106
|
+
fs.writeFileSync(file, `${JSON.stringify(cache, null, 2)}\n`, {
|
|
107
|
+
mode: 0o600,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Update checks must never break MCP startup or tool execution.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function disabledStatus(currentVersion) {
|
|
115
|
+
return {
|
|
116
|
+
checkedAt: new Date().toISOString(),
|
|
117
|
+
cachePath: cachePath(),
|
|
118
|
+
fromCache: false,
|
|
119
|
+
disabled: true,
|
|
120
|
+
mcp: {
|
|
121
|
+
packageName: MCP_PACKAGE,
|
|
122
|
+
currentVersion,
|
|
123
|
+
latestVersion: null,
|
|
124
|
+
outdated: false,
|
|
125
|
+
},
|
|
126
|
+
installer: {
|
|
127
|
+
packageName: INSTALL_PACKAGE,
|
|
128
|
+
currentVersion: null,
|
|
129
|
+
latestVersion: null,
|
|
130
|
+
outdated: false,
|
|
131
|
+
},
|
|
132
|
+
updateAvailable: false,
|
|
133
|
+
blocking: false,
|
|
134
|
+
guidance: null,
|
|
135
|
+
_userNotice: null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function buildNotice(mcp, installer) {
|
|
139
|
+
if (!mcp.outdated)
|
|
140
|
+
return null;
|
|
141
|
+
const versions = mcp.currentVersion && mcp.latestVersion
|
|
142
|
+
? `${mcp.currentVersion} -> ${mcp.latestVersion}`
|
|
143
|
+
: "a newer version is available";
|
|
144
|
+
const installerVersion = installer.latestVersion
|
|
145
|
+
? ` The latest installer is ${installer.latestVersion}.`
|
|
146
|
+
: "";
|
|
147
|
+
return `Sellable update available: ${versions}. Run: npx -y @sellable/install@latest.${installerVersion}`;
|
|
148
|
+
}
|
|
149
|
+
export async function checkForUpdates(options) {
|
|
150
|
+
const currentVersion = readCurrentVersion();
|
|
151
|
+
const ttlMs = options?.ttlMs ??
|
|
152
|
+
(Number(process.env.SELLABLE_UPDATE_CHECK_TTL_MS || "") || DEFAULT_TTL_MS);
|
|
153
|
+
const timeoutMs = options?.timeoutMs ??
|
|
154
|
+
(Number(process.env.SELLABLE_UPDATE_CHECK_TIMEOUT_MS || "") ||
|
|
155
|
+
DEFAULT_TIMEOUT_MS);
|
|
156
|
+
if (process.env.SELLABLE_UPDATE_CHECK_DISABLED === "1") {
|
|
157
|
+
return disabledStatus(currentVersion);
|
|
158
|
+
}
|
|
159
|
+
if (!options?.force) {
|
|
160
|
+
const cached = readCache(ttlMs);
|
|
161
|
+
if (cached)
|
|
162
|
+
return cached;
|
|
163
|
+
}
|
|
164
|
+
const [mcpResult, installerResult] = await Promise.allSettled([
|
|
165
|
+
fetchLatestVersion(MCP_PACKAGE, timeoutMs),
|
|
166
|
+
fetchLatestVersion(INSTALL_PACKAGE, timeoutMs),
|
|
167
|
+
]);
|
|
168
|
+
const mcpLatest = mcpResult.status === "fulfilled" ? mcpResult.value : null;
|
|
169
|
+
const installerLatest = installerResult.status === "fulfilled" ? installerResult.value : null;
|
|
170
|
+
const mcp = {
|
|
171
|
+
packageName: MCP_PACKAGE,
|
|
172
|
+
currentVersion,
|
|
173
|
+
latestVersion: mcpLatest,
|
|
174
|
+
outdated: isVersionOutdated(currentVersion, mcpLatest),
|
|
175
|
+
...(mcpResult.status === "rejected"
|
|
176
|
+
? { error: mcpResult.reason?.message || String(mcpResult.reason) }
|
|
177
|
+
: {}),
|
|
178
|
+
};
|
|
179
|
+
const installer = {
|
|
180
|
+
packageName: INSTALL_PACKAGE,
|
|
181
|
+
currentVersion: null,
|
|
182
|
+
latestVersion: installerLatest,
|
|
183
|
+
outdated: false,
|
|
184
|
+
...(installerResult.status === "rejected"
|
|
185
|
+
? {
|
|
186
|
+
error: installerResult.reason?.message || String(installerResult.reason),
|
|
187
|
+
}
|
|
188
|
+
: {}),
|
|
189
|
+
};
|
|
190
|
+
const notice = buildNotice(mcp, installer);
|
|
191
|
+
const status = {
|
|
192
|
+
checkedAt: new Date().toISOString(),
|
|
193
|
+
cachePath: cachePath(),
|
|
194
|
+
fromCache: false,
|
|
195
|
+
disabled: false,
|
|
196
|
+
mcp,
|
|
197
|
+
installer,
|
|
198
|
+
updateAvailable: mcp.outdated,
|
|
199
|
+
blocking: false,
|
|
200
|
+
guidance: notice ? "Run npx -y @sellable/install@latest" : null,
|
|
201
|
+
_userNotice: notice,
|
|
202
|
+
};
|
|
203
|
+
writeCache(status);
|
|
204
|
+
return status;
|
|
205
|
+
}
|
|
206
|
+
export function logUpdateNotice(status) {
|
|
207
|
+
if (status._userNotice) {
|
|
208
|
+
console.error(`[sellable-mcp] ${status._userNotice}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
package/package.json
CHANGED
|
@@ -118,6 +118,8 @@ Validated draft directory:
|
|
|
118
118
|
loading, runbooks, local skill files, skill versions, or "Sellable token".
|
|
119
119
|
"Quick question panel" is only acceptable when explaining a Codex/Claude setup
|
|
120
120
|
blocker.
|
|
121
|
+
- Before campaign mint, do not call `check_rubric`; Phase 84 only writes and
|
|
122
|
+
validates draft artifacts.
|
|
121
123
|
- Never narrate local draft housekeeping to the user. If you create directories,
|
|
122
124
|
save drafts, write artifacts, or persist intermediate state, translate it into
|
|
123
125
|
the campaign benefit: consistent brief, approved lead source, reviewed message,
|
|
@@ -394,6 +396,9 @@ should test for this campaign. Those can run in parallel and usually take
|
|
|
394
396
|
- Before the user chooses `approve`, the mutating live-campaign tools are
|
|
395
397
|
forbidden: `create_campaign`, `save_rubrics`, `import_leads`,
|
|
396
398
|
`confirm_lead_list`, `update_campaign`, `queue_cells`, and `start_campaign`.
|
|
399
|
+
- During pre-approval validation, do not call `check_rubric`; use the
|
|
400
|
+
campaignless lead-filter artifacts and only use campaign-backed scoring after
|
|
401
|
+
the approval flow allows it.
|
|
397
402
|
- Resume state is based on the presence and completeness of the chained artifacts,
|
|
398
403
|
not on inline validation blocks inside `brief.md`.
|
|
399
404
|
- Preserve the Phase 83 thesis. Do not rewrite product, ICP, offer, or message
|