marketing-cli 0.1.0 → 0.2.0
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 +72 -76
- package/catalogs-manifest.json +24 -0
- package/dist/cli.js +1186 -153
- package/package.json +6 -5
- package/skills/cmo/SKILL.md +73 -7
- package/skills/cmo/rules/brand-file-map.md +193 -0
- package/skills/cmo/rules/command-reference.md +143 -0
- package/skills/cmo/rules/context-switch.md +34 -0
- package/skills/cmo/rules/ecosystem.md +127 -0
- package/skills/cmo/rules/error-recovery.md +94 -0
- package/skills/cmo/rules/learning-loop.md +168 -0
- package/skills/cmo/rules/playbooks.md +215 -0
- package/skills/cmo/rules/progressive-enhancement.md +167 -0
- package/skills/cmo/rules/quality-gate.md +55 -0
- package/skills/cmo/rules/sub-agents.md +135 -0
- package/skills/deepen-plan/SKILL.md +1 -1
- package/skills/direct-response-copy/SKILL.md +1 -1
- package/skills/email-sequences/SKILL.md +1 -1
- package/skills/firecrawl/.skill-meta.json +2 -2
- package/skills/keyword-research/SKILL.md +1 -1
- package/skills/launch-strategy/SKILL.md +2 -2
- package/skills/marketing-psychology/SKILL.md +1 -1
- package/skills/mktg-x/.skill-meta.json +2 -2
- package/skills/positioning-angles/SKILL.md +1 -1
- package/skills/postiz/SKILL.md +248 -0
- package/skills/seo-content/SKILL.md +7 -5
- package/skills/social-campaign/SKILL.md +65 -0
- package/skills-manifest.json +27 -0
package/dist/cli.js
CHANGED
|
@@ -17,10 +17,16 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
17
17
|
var isTTY = () => typeof process.stdout.isTTY === "boolean" && process.stdout.isTTY, isObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v), getNestedValue = (obj, path) => {
|
|
18
18
|
const parts = path.split(".");
|
|
19
19
|
let current = obj;
|
|
20
|
-
for (
|
|
21
|
-
if (current === null || current === undefined
|
|
20
|
+
for (let i = 0;i < parts.length; i++) {
|
|
21
|
+
if (current === null || current === undefined)
|
|
22
22
|
return;
|
|
23
|
-
current
|
|
23
|
+
if (Array.isArray(current)) {
|
|
24
|
+
const remaining = parts.slice(i).join(".");
|
|
25
|
+
return current.map((item) => item !== null && typeof item === "object" && !Array.isArray(item) ? getNestedValue(item, remaining) : undefined);
|
|
26
|
+
}
|
|
27
|
+
if (typeof current !== "object")
|
|
28
|
+
return;
|
|
29
|
+
current = current[parts[i]];
|
|
24
30
|
}
|
|
25
31
|
return current;
|
|
26
32
|
}, setNestedValue = (obj, path, value) => {
|
|
@@ -214,8 +220,8 @@ var package_default;
|
|
|
214
220
|
var init_package = __esm(() => {
|
|
215
221
|
package_default = {
|
|
216
222
|
name: "marketing-cli",
|
|
217
|
-
version: "0.
|
|
218
|
-
description: "Agent-native marketing playbook CLI — one install gives AI agents a full CMO brain with
|
|
223
|
+
version: "0.2.0",
|
|
224
|
+
description: "Agent-native marketing playbook CLI — one install gives AI agents a full CMO brain with 50 skills, 5 research/review agents, 1 upstream catalog (postiz), brand memory, and parallel research.",
|
|
219
225
|
type: "module",
|
|
220
226
|
bin: {
|
|
221
227
|
mktg: "./dist/cli.js"
|
|
@@ -226,6 +232,7 @@ var init_package = __esm(() => {
|
|
|
226
232
|
"skills-manifest.json",
|
|
227
233
|
"agents",
|
|
228
234
|
"agents-manifest.json",
|
|
235
|
+
"catalogs-manifest.json",
|
|
229
236
|
"LICENSE",
|
|
230
237
|
"README.md"
|
|
231
238
|
],
|
|
@@ -256,11 +263,11 @@ var init_package = __esm(() => {
|
|
|
256
263
|
license: "MIT",
|
|
257
264
|
repository: {
|
|
258
265
|
type: "git",
|
|
259
|
-
url: "git+https://github.com/MoizIbnYousaf/
|
|
266
|
+
url: "git+https://github.com/MoizIbnYousaf/marketing-cli.git"
|
|
260
267
|
},
|
|
261
|
-
homepage: "https://github.com/MoizIbnYousaf/
|
|
268
|
+
homepage: "https://github.com/MoizIbnYousaf/marketing-cli#readme",
|
|
262
269
|
bugs: {
|
|
263
|
-
url: "https://github.com/MoizIbnYousaf/
|
|
270
|
+
url: "https://github.com/MoizIbnYousaf/marketing-cli/issues"
|
|
264
271
|
},
|
|
265
272
|
engines: {
|
|
266
273
|
bun: ">=1.1.0",
|
|
@@ -302,10 +309,17 @@ var init_types = __esm(() => {
|
|
|
302
309
|
];
|
|
303
310
|
});
|
|
304
311
|
|
|
312
|
+
// src/constants.ts
|
|
313
|
+
var GITHUB_REPO_URL = "https://github.com/MoizIbnYousaf/marketing-cli", GITHUB_REPO_ISSUES_URL, GITHUB_REPO_BLOB_MAIN;
|
|
314
|
+
var init_constants = __esm(() => {
|
|
315
|
+
GITHUB_REPO_ISSUES_URL = `${GITHUB_REPO_URL}/issues`;
|
|
316
|
+
GITHUB_REPO_BLOB_MAIN = `${GITHUB_REPO_URL}/blob/main`;
|
|
317
|
+
});
|
|
318
|
+
|
|
305
319
|
// src/core/errors.ts
|
|
306
320
|
import { resolve, relative, isAbsolute } from "node:path";
|
|
307
321
|
import { lstatSync } from "node:fs";
|
|
308
|
-
var DOCS, notFound = (what, suggestions = [], docs) => err("NOT_FOUND", `${what} not found`, suggestions, 1, docs), invalidArgs = (message, suggestions = [], docs) => err("INVALID_ARGS", message, suggestions, 2, docs), missingDep = (dep, suggestions = [], docs) => err("MISSING_DEPENDENCY", `Required dependency not found: ${dep}`, suggestions, 3, docs), networkError = (message, docs) => err("NETWORK_ERROR", message, ["Check your internet connection"], 5, docs),
|
|
322
|
+
var DOCS, notFound = (what, suggestions = [], docs) => err("NOT_FOUND", `${what} not found`, suggestions, 1, docs), invalidArgs = (message, suggestions = [], docs) => err("INVALID_ARGS", message, suggestions, 2, docs), missingDep = (dep, suggestions = [], docs) => err("MISSING_DEPENDENCY", `Required dependency not found: ${dep}`, suggestions, 3, docs), networkError = (message, docs) => err("NETWORK_ERROR", message, ["Check your internet connection"], 5, docs), sandboxPath = (root, untrusted) => {
|
|
309
323
|
if (isAbsolute(untrusted)) {
|
|
310
324
|
return { ok: false, message: "Absolute paths are not allowed" };
|
|
311
325
|
}
|
|
@@ -382,10 +396,11 @@ var DOCS, notFound = (what, suggestions = [], docs) => err("NOT_FOUND", `${what}
|
|
|
382
396
|
};
|
|
383
397
|
var init_errors = __esm(() => {
|
|
384
398
|
init_types();
|
|
399
|
+
init_constants();
|
|
385
400
|
DOCS = {
|
|
386
|
-
skills:
|
|
387
|
-
brand:
|
|
388
|
-
commands:
|
|
401
|
+
skills: `${GITHUB_REPO_BLOB_MAIN}/docs/skill-contract.md`,
|
|
402
|
+
brand: `${GITHUB_REPO_BLOB_MAIN}/brand/SCHEMA.md`,
|
|
403
|
+
commands: `${GITHUB_REPO_URL}#commands`
|
|
389
404
|
};
|
|
390
405
|
CONTROL_CHAR_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F]/;
|
|
391
406
|
VALID_RESOURCE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
@@ -983,6 +998,10 @@ Be specific about metrics when available (CTR, views, signups).
|
|
|
983
998
|
});
|
|
984
999
|
|
|
985
1000
|
// src/core/paths.ts
|
|
1001
|
+
var exports_paths = {};
|
|
1002
|
+
__export(exports_paths, {
|
|
1003
|
+
getPackageRoot: () => getPackageRoot
|
|
1004
|
+
});
|
|
986
1005
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
987
1006
|
import { fileURLToPath } from "node:url";
|
|
988
1007
|
var moduleDir = () => dirname2(fileURLToPath(import.meta.url)), getPackageRoot = () => {
|
|
@@ -995,6 +1014,7 @@ var moduleDir = () => dirname2(fileURLToPath(import.meta.url)), getPackageRoot =
|
|
|
995
1014
|
var init_paths = () => {};
|
|
996
1015
|
|
|
997
1016
|
// src/core/skills.ts
|
|
1017
|
+
import { spawnSync } from "node:child_process";
|
|
998
1018
|
import { join as join4, dirname as dirname3 } from "node:path";
|
|
999
1019
|
import { mkdir as mkdir3, chmod, stat } from "node:fs/promises";
|
|
1000
1020
|
import { homedir } from "node:os";
|
|
@@ -1145,7 +1165,56 @@ var SKILLS_INSTALL_DIR, loadManifest = async () => {
|
|
|
1145
1165
|
}
|
|
1146
1166
|
}
|
|
1147
1167
|
return { updated, unchanged, notBundled, versionChanges };
|
|
1148
|
-
}, getSkillsInstallDir = () => SKILLS_INSTALL_DIR,
|
|
1168
|
+
}, getSkillsInstallDir = () => SKILLS_INSTALL_DIR, findAiAgentSkillsBinary = () => {
|
|
1169
|
+
const pathValue = process.env.PATH ?? "";
|
|
1170
|
+
for (const segment of pathValue.split(":")) {
|
|
1171
|
+
if (!segment)
|
|
1172
|
+
continue;
|
|
1173
|
+
const candidate = join4(segment, "ai-agent-skills");
|
|
1174
|
+
try {
|
|
1175
|
+
const result = spawnSync(candidate, ["--version"], {
|
|
1176
|
+
encoding: "utf8",
|
|
1177
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1178
|
+
});
|
|
1179
|
+
if (result.status === 0) {
|
|
1180
|
+
return candidate;
|
|
1181
|
+
}
|
|
1182
|
+
} catch {}
|
|
1183
|
+
}
|
|
1184
|
+
return null;
|
|
1185
|
+
}, installSkillsWithAiAgentSkills = async (manifest, dryRun = false) => {
|
|
1186
|
+
const binary = findAiAgentSkillsBinary();
|
|
1187
|
+
if (!binary) {
|
|
1188
|
+
return { installed: [], skipped: [], failed: [], delegated: false, binary: null };
|
|
1189
|
+
}
|
|
1190
|
+
const args = ["mktg"];
|
|
1191
|
+
if (dryRun) {
|
|
1192
|
+
args.push("--dry-run");
|
|
1193
|
+
}
|
|
1194
|
+
args.push("--format", "json");
|
|
1195
|
+
const result = spawnSync(binary, args, {
|
|
1196
|
+
encoding: "utf8",
|
|
1197
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1198
|
+
env: process.env
|
|
1199
|
+
});
|
|
1200
|
+
if (result.status !== 0) {
|
|
1201
|
+
const detail = (result.stderr || result.stdout || `exit ${result.status ?? 1}`).trim();
|
|
1202
|
+
return {
|
|
1203
|
+
installed: [],
|
|
1204
|
+
skipped: [],
|
|
1205
|
+
failed: [{ name: "_delegate", reason: detail }],
|
|
1206
|
+
delegated: false,
|
|
1207
|
+
binary
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
installed: getSkillNames(manifest),
|
|
1212
|
+
skipped: [],
|
|
1213
|
+
failed: [],
|
|
1214
|
+
delegated: true,
|
|
1215
|
+
binary
|
|
1216
|
+
};
|
|
1217
|
+
}, findExternalSkill = async (nameOrPath) => {
|
|
1149
1218
|
const home = homedir();
|
|
1150
1219
|
if (nameOrPath.includes("/")) {
|
|
1151
1220
|
const resolved = nameOrPath.startsWith("~") ? nameOrPath.replace("~", home) : nameOrPath;
|
|
@@ -2208,13 +2277,263 @@ var init_doctor_fix = __esm(() => {
|
|
|
2208
2277
|
};
|
|
2209
2278
|
});
|
|
2210
2279
|
|
|
2280
|
+
// src/core/catalogs.ts
|
|
2281
|
+
var exports_catalogs = {};
|
|
2282
|
+
__export(exports_catalogs, {
|
|
2283
|
+
loadCatalogManifest: () => loadCatalogManifest,
|
|
2284
|
+
isLinkSafeLicense: () => isLinkSafeLicense,
|
|
2285
|
+
isCopyleftLicense: () => isCopyleftLicense,
|
|
2286
|
+
getCatalogNames: () => getCatalogNames,
|
|
2287
|
+
getCatalog: () => getCatalog,
|
|
2288
|
+
detectLicenseDenials: () => detectLicenseDenials,
|
|
2289
|
+
detectAdapterCollisions: () => detectAdapterCollisions,
|
|
2290
|
+
computeConfiguredStatus: () => computeConfiguredStatus,
|
|
2291
|
+
_testing: () => _testing,
|
|
2292
|
+
DEFAULT_BUILTIN_PUBLISH_ADAPTERS: () => DEFAULT_BUILTIN_PUBLISH_ADAPTERS
|
|
2293
|
+
});
|
|
2294
|
+
import { join as join7 } from "node:path";
|
|
2295
|
+
var LINK_SAFE_LICENSES, COPYLEFT_LICENSES, isLinkSafeLicense = (license) => LINK_SAFE_LICENSES.includes(license), isCopyleftLicense = (license) => COPYLEFT_LICENSES.includes(license), VALID_CATALOG_NAME_RE, AUTH_STYLES, TRANSPORTS, expectedCredentialEnvCount = (style) => {
|
|
2296
|
+
switch (style) {
|
|
2297
|
+
case "bearer":
|
|
2298
|
+
return { min: 1, max: 1 };
|
|
2299
|
+
case "basic":
|
|
2300
|
+
return { min: 2, max: 2 };
|
|
2301
|
+
case "oauth2":
|
|
2302
|
+
return { min: 3, max: Number.POSITIVE_INFINITY };
|
|
2303
|
+
case "none":
|
|
2304
|
+
return { min: 0, max: 0 };
|
|
2305
|
+
default:
|
|
2306
|
+
return { min: 0, max: Number.POSITIVE_INFINITY };
|
|
2307
|
+
}
|
|
2308
|
+
}, detectAdapterCollisions = (manifest, hardcoded = { publish_adapters: [] }) => {
|
|
2309
|
+
const seen = new Map;
|
|
2310
|
+
for (const name of hardcoded.publish_adapters) {
|
|
2311
|
+
seen.set(`publish_adapters:${name}`, ["<builtin>"]);
|
|
2312
|
+
}
|
|
2313
|
+
const kinds = ["publish_adapters", "scheduling_adapters", "email_adapters"];
|
|
2314
|
+
for (const [catName, cat] of Object.entries(manifest.catalogs)) {
|
|
2315
|
+
for (const kind of kinds) {
|
|
2316
|
+
const adapters = cat.capabilities[kind] ?? [];
|
|
2317
|
+
for (const adapter of adapters) {
|
|
2318
|
+
const key = `${kind}:${adapter}`;
|
|
2319
|
+
const prior = seen.get(key);
|
|
2320
|
+
if (prior)
|
|
2321
|
+
seen.set(key, [...prior, catName]);
|
|
2322
|
+
else
|
|
2323
|
+
seen.set(key, [catName]);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2327
|
+
const collisions = [];
|
|
2328
|
+
for (const [key, declaredBy] of seen) {
|
|
2329
|
+
if (declaredBy.length < 2)
|
|
2330
|
+
continue;
|
|
2331
|
+
const colonIdx = key.indexOf(":");
|
|
2332
|
+
const kind = key.slice(0, colonIdx);
|
|
2333
|
+
const adapter = key.slice(colonIdx + 1);
|
|
2334
|
+
collisions.push({ kind, adapter, declaredBy });
|
|
2335
|
+
}
|
|
2336
|
+
return collisions;
|
|
2337
|
+
}, detectLicenseDenials = (manifest) => {
|
|
2338
|
+
const denials = [];
|
|
2339
|
+
for (const [name, cat] of Object.entries(manifest.catalogs)) {
|
|
2340
|
+
if (!isCopyleftLicense(cat.license))
|
|
2341
|
+
continue;
|
|
2342
|
+
if (cat.transport !== "http" || cat.sdk_reference !== null) {
|
|
2343
|
+
denials.push({
|
|
2344
|
+
catalog: name,
|
|
2345
|
+
license: cat.license,
|
|
2346
|
+
transport: cat.transport,
|
|
2347
|
+
sdk_reference: cat.sdk_reference,
|
|
2348
|
+
reason: "sdk-link-on-copyleft-license"
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return denials;
|
|
2353
|
+
}, validateShape = (raw) => {
|
|
2354
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2355
|
+
return { ok: false, detail: "manifest must be a JSON object", path: "" };
|
|
2356
|
+
}
|
|
2357
|
+
const m = raw;
|
|
2358
|
+
if (typeof m.version !== "number") {
|
|
2359
|
+
return { ok: false, detail: "missing or non-number 'version' field", path: "version" };
|
|
2360
|
+
}
|
|
2361
|
+
if (m.catalogs === null || typeof m.catalogs !== "object" || Array.isArray(m.catalogs)) {
|
|
2362
|
+
return { ok: false, detail: "'catalogs' must be an object", path: "catalogs" };
|
|
2363
|
+
}
|
|
2364
|
+
const catalogs = m.catalogs;
|
|
2365
|
+
for (const [name, entry] of Object.entries(catalogs)) {
|
|
2366
|
+
if (!VALID_CATALOG_NAME_RE.test(name)) {
|
|
2367
|
+
return { ok: false, detail: `catalog key '${name}' is not a valid resource id`, path: `catalogs.${name}` };
|
|
2368
|
+
}
|
|
2369
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
2370
|
+
return { ok: false, detail: "catalog entry must be an object", path: `catalogs.${name}` };
|
|
2371
|
+
}
|
|
2372
|
+
const e = entry;
|
|
2373
|
+
for (const required of ["name", "repo_url", "docs_url", "license", "version_pinned", "capabilities", "transport", "auth", "skills"]) {
|
|
2374
|
+
if (!(required in e)) {
|
|
2375
|
+
return { ok: false, detail: `missing required field '${required}'`, path: `catalogs.${name}.${required}` };
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
if (typeof e.name !== "string" || e.name !== name) {
|
|
2379
|
+
return { ok: false, detail: "entry 'name' must equal the object key", path: `catalogs.${name}.name` };
|
|
2380
|
+
}
|
|
2381
|
+
if (typeof e.repo_url !== "string")
|
|
2382
|
+
return { ok: false, detail: "'repo_url' must be a string", path: `catalogs.${name}.repo_url` };
|
|
2383
|
+
if (typeof e.docs_url !== "string")
|
|
2384
|
+
return { ok: false, detail: "'docs_url' must be a string", path: `catalogs.${name}.docs_url` };
|
|
2385
|
+
if (typeof e.license !== "string")
|
|
2386
|
+
return { ok: false, detail: "'license' must be a string", path: `catalogs.${name}.license` };
|
|
2387
|
+
if (typeof e.version_pinned !== "string")
|
|
2388
|
+
return { ok: false, detail: "'version_pinned' must be a string", path: `catalogs.${name}.version_pinned` };
|
|
2389
|
+
if (typeof e.transport !== "string" || !TRANSPORTS.has(e.transport)) {
|
|
2390
|
+
return { ok: false, detail: "'transport' must be 'sdk' or 'http'", path: `catalogs.${name}.transport` };
|
|
2391
|
+
}
|
|
2392
|
+
if (e.transport === "sdk") {
|
|
2393
|
+
if (typeof e.sdk_reference !== "string" || e.sdk_reference.length === 0) {
|
|
2394
|
+
return { ok: false, detail: "'sdk_reference' must be a non-empty string when transport='sdk'", path: `catalogs.${name}.sdk_reference` };
|
|
2395
|
+
}
|
|
2396
|
+
} else {
|
|
2397
|
+
if (e.sdk_reference !== null) {
|
|
2398
|
+
return { ok: false, detail: "'sdk_reference' must be null when transport='http'", path: `catalogs.${name}.sdk_reference` };
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
if (e.capabilities === null || typeof e.capabilities !== "object" || Array.isArray(e.capabilities)) {
|
|
2402
|
+
return { ok: false, detail: "'capabilities' must be an object", path: `catalogs.${name}.capabilities` };
|
|
2403
|
+
}
|
|
2404
|
+
const caps = e.capabilities;
|
|
2405
|
+
let hasAnyAdapter = false;
|
|
2406
|
+
for (const kind of ["publish_adapters", "scheduling_adapters", "email_adapters"]) {
|
|
2407
|
+
const arr = caps[kind];
|
|
2408
|
+
if (arr === undefined)
|
|
2409
|
+
continue;
|
|
2410
|
+
if (!Array.isArray(arr)) {
|
|
2411
|
+
return { ok: false, detail: `'capabilities.${kind}' must be an array of strings`, path: `catalogs.${name}.capabilities.${kind}` };
|
|
2412
|
+
}
|
|
2413
|
+
for (const item of arr) {
|
|
2414
|
+
if (typeof item !== "string" || !VALID_CATALOG_NAME_RE.test(item)) {
|
|
2415
|
+
return { ok: false, detail: `'capabilities.${kind}' contains invalid adapter name`, path: `catalogs.${name}.capabilities.${kind}` };
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
if (arr.length > 0)
|
|
2419
|
+
hasAnyAdapter = true;
|
|
2420
|
+
}
|
|
2421
|
+
if (!Array.isArray(e.skills)) {
|
|
2422
|
+
return { ok: false, detail: "'skills' must be an array of strings", path: `catalogs.${name}.skills` };
|
|
2423
|
+
}
|
|
2424
|
+
for (const skill of e.skills) {
|
|
2425
|
+
if (typeof skill !== "string" || !VALID_CATALOG_NAME_RE.test(skill)) {
|
|
2426
|
+
return { ok: false, detail: "'skills' contains invalid skill name", path: `catalogs.${name}.skills` };
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
if (!hasAnyAdapter && e.skills.length === 0) {
|
|
2430
|
+
return { ok: false, detail: "catalog must declare at least one adapter OR one skill", path: `catalogs.${name}` };
|
|
2431
|
+
}
|
|
2432
|
+
if (e.auth === null || typeof e.auth !== "object" || Array.isArray(e.auth)) {
|
|
2433
|
+
return { ok: false, detail: "'auth' must be an object", path: `catalogs.${name}.auth` };
|
|
2434
|
+
}
|
|
2435
|
+
const auth = e.auth;
|
|
2436
|
+
if (typeof auth.style !== "string" || !AUTH_STYLES.has(auth.style)) {
|
|
2437
|
+
return { ok: false, detail: "'auth.style' must be 'bearer' | 'basic' | 'oauth2' | 'none'", path: `catalogs.${name}.auth.style` };
|
|
2438
|
+
}
|
|
2439
|
+
if (typeof auth.base_env !== "string") {
|
|
2440
|
+
return { ok: false, detail: "'auth.base_env' must be a string", path: `catalogs.${name}.auth.base_env` };
|
|
2441
|
+
}
|
|
2442
|
+
if (!Array.isArray(auth.credential_envs) || !auth.credential_envs.every((x) => typeof x === "string")) {
|
|
2443
|
+
return { ok: false, detail: "'auth.credential_envs' must be a string array", path: `catalogs.${name}.auth.credential_envs` };
|
|
2444
|
+
}
|
|
2445
|
+
const credCount = auth.credential_envs.length;
|
|
2446
|
+
const { min, max } = expectedCredentialEnvCount(auth.style);
|
|
2447
|
+
if (credCount < min || credCount > max) {
|
|
2448
|
+
return {
|
|
2449
|
+
ok: false,
|
|
2450
|
+
detail: `auth.style '${auth.style}' requires ${min === max ? min : `${min}+`} credential_envs, got ${credCount}`,
|
|
2451
|
+
path: `catalogs.${name}.auth.credential_envs`
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
if (auth.header_format !== undefined && auth.header_format !== "bearer" && auth.header_format !== "bare") {
|
|
2455
|
+
return { ok: false, detail: "'auth.header_format' must be 'bearer' or 'bare' when present", path: `catalogs.${name}.auth.header_format` };
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
return { ok: true, manifest: raw };
|
|
2459
|
+
}, DEFAULT_BUILTIN_PUBLISH_ADAPTERS, loadCatalogManifest = async (hardcoded = { publish_adapters: DEFAULT_BUILTIN_PUBLISH_ADAPTERS }) => {
|
|
2460
|
+
const manifestPath = join7(getPackageRoot(), "catalogs-manifest.json");
|
|
2461
|
+
const file = Bun.file(manifestPath);
|
|
2462
|
+
const exists = await file.exists();
|
|
2463
|
+
if (!exists) {
|
|
2464
|
+
return { ok: false, reason: "manifest-missing", path: manifestPath };
|
|
2465
|
+
}
|
|
2466
|
+
const raw = await file.text();
|
|
2467
|
+
const parsed = parseJsonInput(raw);
|
|
2468
|
+
if (!parsed.ok) {
|
|
2469
|
+
return { ok: false, reason: "manifest-invalid", detail: parsed.message, path: manifestPath };
|
|
2470
|
+
}
|
|
2471
|
+
const shape = validateShape(parsed.data);
|
|
2472
|
+
if (!shape.ok) {
|
|
2473
|
+
return { ok: false, reason: "manifest-invalid", detail: shape.detail, path: shape.path };
|
|
2474
|
+
}
|
|
2475
|
+
const denials = detectLicenseDenials(shape.manifest);
|
|
2476
|
+
if (denials.length > 0) {
|
|
2477
|
+
return { ok: false, reason: "license-denied", denials };
|
|
2478
|
+
}
|
|
2479
|
+
const collisions = detectAdapterCollisions(shape.manifest, hardcoded);
|
|
2480
|
+
if (collisions.length > 0) {
|
|
2481
|
+
return { ok: false, reason: "collision", collisions };
|
|
2482
|
+
}
|
|
2483
|
+
return { ok: true, manifest: shape.manifest };
|
|
2484
|
+
}, getCatalogNames = (manifest) => Object.keys(manifest.catalogs), getCatalog = (manifest, name) => manifest.catalogs[name] ?? null, computeConfiguredStatus = (entry, env = process.env) => {
|
|
2485
|
+
const missing = [];
|
|
2486
|
+
if (!env[entry.auth.base_env])
|
|
2487
|
+
missing.push(entry.auth.base_env);
|
|
2488
|
+
for (const cred of entry.auth.credential_envs) {
|
|
2489
|
+
if (!env[cred])
|
|
2490
|
+
missing.push(cred);
|
|
2491
|
+
}
|
|
2492
|
+
return {
|
|
2493
|
+
configured: missing.length === 0,
|
|
2494
|
+
missingEnvs: missing,
|
|
2495
|
+
resolvedBase: env[entry.auth.base_env] ?? null
|
|
2496
|
+
};
|
|
2497
|
+
}, _testing;
|
|
2498
|
+
var init_catalogs = __esm(() => {
|
|
2499
|
+
init_paths();
|
|
2500
|
+
init_errors();
|
|
2501
|
+
LINK_SAFE_LICENSES = [
|
|
2502
|
+
"MIT",
|
|
2503
|
+
"Apache-2.0",
|
|
2504
|
+
"BSD-2-Clause",
|
|
2505
|
+
"BSD-3-Clause",
|
|
2506
|
+
"ISC"
|
|
2507
|
+
];
|
|
2508
|
+
COPYLEFT_LICENSES = [
|
|
2509
|
+
"AGPL-3.0",
|
|
2510
|
+
"AGPL-3.0-or-later",
|
|
2511
|
+
"AGPL-3.0-only",
|
|
2512
|
+
"GPL-3.0",
|
|
2513
|
+
"GPL-3.0-or-later",
|
|
2514
|
+
"GPL-3.0-only",
|
|
2515
|
+
"LGPL-3.0",
|
|
2516
|
+
"LGPL-3.0-or-later",
|
|
2517
|
+
"LGPL-3.0-only"
|
|
2518
|
+
];
|
|
2519
|
+
VALID_CATALOG_NAME_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
|
2520
|
+
AUTH_STYLES = new Set(["bearer", "basic", "oauth2", "none"]);
|
|
2521
|
+
TRANSPORTS = new Set(["sdk", "http"]);
|
|
2522
|
+
DEFAULT_BUILTIN_PUBLISH_ADAPTERS = ["typefully", "resend", "file"];
|
|
2523
|
+
_testing = {
|
|
2524
|
+
validateShape,
|
|
2525
|
+
LINK_SAFE_LICENSES,
|
|
2526
|
+
COPYLEFT_LICENSES
|
|
2527
|
+
};
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2211
2530
|
// src/commands/doctor.ts
|
|
2212
2531
|
var exports_doctor = {};
|
|
2213
2532
|
__export(exports_doctor, {
|
|
2214
2533
|
schema: () => schema,
|
|
2215
2534
|
handler: () => handler
|
|
2216
2535
|
});
|
|
2217
|
-
import { join as
|
|
2536
|
+
import { join as join8 } from "node:path";
|
|
2218
2537
|
var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red("●") : yellow("●"), fixIndicator = (r) => r === "fixed" ? green("✓") : r === "skipped" ? yellow("−") : red("✗"), checkBrand = async (cwd) => {
|
|
2219
2538
|
const checks = [];
|
|
2220
2539
|
const statuses = await getBrandStatus(cwd);
|
|
@@ -2233,7 +2552,7 @@ var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red(
|
|
|
2233
2552
|
const templateFiles = [];
|
|
2234
2553
|
for (const s of existingProfiles) {
|
|
2235
2554
|
try {
|
|
2236
|
-
const content = await Bun.file(
|
|
2555
|
+
const content = await Bun.file(join8(cwd, "brand", s.file)).text();
|
|
2237
2556
|
if (isTemplateContent(s.file, content))
|
|
2238
2557
|
templateFiles.push(s.file);
|
|
2239
2558
|
} catch {}
|
|
@@ -2339,7 +2658,7 @@ var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red(
|
|
|
2339
2658
|
const checks = [];
|
|
2340
2659
|
let healthy = 0;
|
|
2341
2660
|
for (const ext of entries) {
|
|
2342
|
-
const skillMdPath =
|
|
2661
|
+
const skillMdPath = join8(ext.source_path, "SKILL.md");
|
|
2343
2662
|
const exists = await Bun.file(skillMdPath).exists();
|
|
2344
2663
|
if (!exists) {
|
|
2345
2664
|
checks.push({
|
|
@@ -2377,17 +2696,55 @@ var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red(
|
|
|
2377
2696
|
});
|
|
2378
2697
|
}
|
|
2379
2698
|
return checks;
|
|
2699
|
+
}, checkCatalogs = async () => {
|
|
2700
|
+
try {
|
|
2701
|
+
const { loadCatalogManifest: loadCatalogManifest2, computeConfiguredStatus: computeConfiguredStatus2 } = await Promise.resolve().then(() => (init_catalogs(), exports_catalogs));
|
|
2702
|
+
const result = await loadCatalogManifest2();
|
|
2703
|
+
if (!result.ok) {
|
|
2704
|
+
if (result.reason === "manifest-missing")
|
|
2705
|
+
return [];
|
|
2706
|
+
const detail = result.reason === "manifest-invalid" ? `catalogs-manifest.json is malformed: ${result.detail}` : result.reason === "collision" ? `catalogs-manifest.json has adapter-name collisions (${result.collisions.length})` : `catalogs-manifest.json has ${result.denials.length} license denial(s)`;
|
|
2707
|
+
return [{
|
|
2708
|
+
name: "catalogs-manifest",
|
|
2709
|
+
status: "fail",
|
|
2710
|
+
detail,
|
|
2711
|
+
fix: "mktg catalog list --json (shows structured error envelope)"
|
|
2712
|
+
}];
|
|
2713
|
+
}
|
|
2714
|
+
const checks = [];
|
|
2715
|
+
for (const cat of Object.values(result.manifest.catalogs)) {
|
|
2716
|
+
const status = computeConfiguredStatus2(cat);
|
|
2717
|
+
if (status.configured) {
|
|
2718
|
+
checks.push({
|
|
2719
|
+
name: `catalog-${cat.name}`,
|
|
2720
|
+
status: "pass",
|
|
2721
|
+
detail: `${cat.name} configured (${cat.transport} → ${status.resolvedBase})`
|
|
2722
|
+
});
|
|
2723
|
+
} else {
|
|
2724
|
+
checks.push({
|
|
2725
|
+
name: `catalog-${cat.name}`,
|
|
2726
|
+
status: "warn",
|
|
2727
|
+
detail: `${cat.name} unconfigured — missing: ${status.missingEnvs.join(", ")}`,
|
|
2728
|
+
fix: `mktg catalog info ${cat.name} --json (shows required env vars + docs_url)`
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
return checks;
|
|
2733
|
+
} catch {
|
|
2734
|
+
return [];
|
|
2735
|
+
}
|
|
2380
2736
|
}, runAllChecks = async (cwd) => {
|
|
2381
|
-
const [brand, skills, agents, graph, clis, integrations, externalSkills] = await Promise.all([
|
|
2737
|
+
const [brand, skills, agents, graph, clis, integrations, externalSkills, catalogs] = await Promise.all([
|
|
2382
2738
|
checkBrand(cwd),
|
|
2383
2739
|
checkSkills(),
|
|
2384
2740
|
checkAgents(),
|
|
2385
2741
|
checkGraph(),
|
|
2386
2742
|
checkCLIs(),
|
|
2387
2743
|
checkIntegrations(),
|
|
2388
|
-
checkExternalSkills(cwd)
|
|
2744
|
+
checkExternalSkills(cwd),
|
|
2745
|
+
checkCatalogs()
|
|
2389
2746
|
]);
|
|
2390
|
-
return { brand, skills, agents, graph, clis, integrations, externalSkills };
|
|
2747
|
+
return { brand, skills, agents, graph, clis, integrations, externalSkills, catalogs };
|
|
2391
2748
|
}, printChecks = (sections, fixes) => {
|
|
2392
2749
|
writeStderr("");
|
|
2393
2750
|
writeStderr(` ${bold("mktg doctor")}`);
|
|
@@ -2408,6 +2765,8 @@ var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red(
|
|
|
2408
2765
|
printSection("External Skills", sections.externalSkills);
|
|
2409
2766
|
if (sections.integrations.length > 0)
|
|
2410
2767
|
printSection("Integrations", sections.integrations);
|
|
2768
|
+
if (sections.catalogs.length > 0)
|
|
2769
|
+
printSection("Catalogs", sections.catalogs);
|
|
2411
2770
|
if (fixes && fixes.length > 0) {
|
|
2412
2771
|
writeStderr(` ${dim("Fixes")}`);
|
|
2413
2772
|
for (const f of fixes) {
|
|
@@ -2415,7 +2774,7 @@ var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red(
|
|
|
2415
2774
|
}
|
|
2416
2775
|
writeStderr("");
|
|
2417
2776
|
}
|
|
2418
|
-
const all = [...sections.brand, ...sections.skills, ...sections.agents, ...sections.graph, ...sections.clis, ...sections.externalSkills, ...sections.integrations];
|
|
2777
|
+
const all = [...sections.brand, ...sections.skills, ...sections.agents, ...sections.graph, ...sections.clis, ...sections.externalSkills, ...sections.integrations, ...sections.catalogs];
|
|
2419
2778
|
const passed = !all.some((c) => c.status === "fail");
|
|
2420
2779
|
if (passed) {
|
|
2421
2780
|
writeStderr(` ${green(bold("All checks pass"))}`);
|
|
@@ -2461,13 +2820,13 @@ var schema, indicator = (s) => s === "pass" ? green("●") : s === "fail" ? red(
|
|
|
2461
2820
|
}
|
|
2462
2821
|
const wantsFix = args.includes("--fix");
|
|
2463
2822
|
let sections = await runAllChecks(flags.cwd);
|
|
2464
|
-
let allChecks = [...sections.brand, ...sections.skills, ...sections.agents, ...sections.graph, ...sections.clis, ...sections.externalSkills, ...sections.integrations];
|
|
2823
|
+
let allChecks = [...sections.brand, ...sections.skills, ...sections.agents, ...sections.graph, ...sections.clis, ...sections.externalSkills, ...sections.integrations, ...sections.catalogs];
|
|
2465
2824
|
let fixes;
|
|
2466
2825
|
if (wantsFix) {
|
|
2467
2826
|
fixes = await executeDoctor(allChecks, flags.cwd, flags.dryRun);
|
|
2468
2827
|
if (!flags.dryRun && fixes.some((f) => f.result === "fixed")) {
|
|
2469
2828
|
sections = await runAllChecks(flags.cwd);
|
|
2470
|
-
allChecks = [...sections.brand, ...sections.skills, ...sections.agents, ...sections.graph, ...sections.clis, ...sections.integrations];
|
|
2829
|
+
allChecks = [...sections.brand, ...sections.skills, ...sections.agents, ...sections.graph, ...sections.clis, ...sections.integrations, ...sections.catalogs];
|
|
2471
2830
|
}
|
|
2472
2831
|
}
|
|
2473
2832
|
const passed = !allChecks.some((c) => c.status === "fail");
|
|
@@ -2514,7 +2873,7 @@ __export(exports_init, {
|
|
|
2514
2873
|
schema: () => schema2,
|
|
2515
2874
|
handler: () => handler2
|
|
2516
2875
|
});
|
|
2517
|
-
import { join as
|
|
2876
|
+
import { join as join9 } from "node:path";
|
|
2518
2877
|
import { mkdir as mkdir6 } from "node:fs/promises";
|
|
2519
2878
|
var schema2, parseInitFlags = (args) => {
|
|
2520
2879
|
let skipBrand = false;
|
|
@@ -2582,7 +2941,7 @@ var schema2, parseInitFlags = (args) => {
|
|
|
2582
2941
|
return null;
|
|
2583
2942
|
}
|
|
2584
2943
|
}, populateFromScrape = async (cwd, scraped, dryRun) => {
|
|
2585
|
-
const brandDir =
|
|
2944
|
+
const brandDir = join9(cwd, "brand");
|
|
2586
2945
|
if (!dryRun)
|
|
2587
2946
|
await mkdir6(brandDir, { recursive: true });
|
|
2588
2947
|
const populated = [];
|
|
@@ -2665,18 +3024,18 @@ ${scraped.url}
|
|
|
2665
3024
|
for (const [file, content] of Object.entries(files)) {
|
|
2666
3025
|
populated.push(file);
|
|
2667
3026
|
if (!dryRun) {
|
|
2668
|
-
writes.push(Bun.write(
|
|
3027
|
+
writes.push(Bun.write(join9(brandDir, file), content).then(() => {}));
|
|
2669
3028
|
}
|
|
2670
3029
|
}
|
|
2671
3030
|
await Promise.all(writes);
|
|
2672
3031
|
return populated;
|
|
2673
3032
|
}, detectProject = async (cwd) => {
|
|
2674
|
-
const pkgFile = Bun.file(
|
|
2675
|
-
const readmeFile = Bun.file(
|
|
3033
|
+
const pkgFile = Bun.file(join9(cwd, "package.json"));
|
|
3034
|
+
const readmeFile = Bun.file(join9(cwd, "README.md"));
|
|
2676
3035
|
const checkBrandDir = async () => {
|
|
2677
3036
|
try {
|
|
2678
3037
|
const { stat: stat3 } = await import("node:fs/promises");
|
|
2679
|
-
const s = await stat3(
|
|
3038
|
+
const s = await stat3(join9(cwd, "brand"));
|
|
2680
3039
|
return s.isDirectory();
|
|
2681
3040
|
} catch {
|
|
2682
3041
|
return false;
|
|
@@ -2703,12 +3062,9 @@ ${scraped.url}
|
|
|
2703
3062
|
return invalidArgs(parsed.message);
|
|
2704
3063
|
return ok(parsed.data);
|
|
2705
3064
|
}
|
|
2706
|
-
if (flags.yes) {
|
|
3065
|
+
if (flags.yes || !isTTY()) {
|
|
2707
3066
|
return ok({ business: projectName, goal: "launch" });
|
|
2708
3067
|
}
|
|
2709
|
-
if (!isTTY()) {
|
|
2710
|
-
return missingInput('{"business":"My App","goal":"launch"}');
|
|
2711
|
-
}
|
|
2712
3068
|
const readline = await import("node:readline");
|
|
2713
3069
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
2714
3070
|
const ask = (question, defaultVal) => new Promise((resolve2) => {
|
|
@@ -2788,17 +3144,32 @@ ${scraped.url}
|
|
|
2788
3144
|
}
|
|
2789
3145
|
try {
|
|
2790
3146
|
const manifest = await loadManifest();
|
|
2791
|
-
const
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
3147
|
+
const delegated = await installSkillsWithAiAgentSkills(manifest, flags.dryRun);
|
|
3148
|
+
if (delegated.delegated) {
|
|
3149
|
+
skillsResult = {
|
|
3150
|
+
installed: delegated.installed,
|
|
3151
|
+
skipped: delegated.skipped,
|
|
3152
|
+
failed: delegated.failed.map(({ name, reason }) => `${name}:${reason}`)
|
|
3153
|
+
};
|
|
3154
|
+
if (isTTY() && !flags.json) {
|
|
3155
|
+
writeStderr(` ${green("✓")} ${skillsResult.installed.length} skills installed via ai-agent-skills`);
|
|
3156
|
+
}
|
|
3157
|
+
} else {
|
|
3158
|
+
if (delegated.binary && delegated.failed.length > 0 && isTTY() && !flags.json) {
|
|
3159
|
+
writeStderr(` ${yellow("●")} ai-agent-skills delegation failed — falling back to direct install`);
|
|
3160
|
+
}
|
|
3161
|
+
const installedSkills = await installSkills(manifest, flags.dryRun, flags.cwd);
|
|
3162
|
+
skillsResult = {
|
|
3163
|
+
installed: installedSkills.installed,
|
|
3164
|
+
skipped: installedSkills.skipped,
|
|
3165
|
+
failed: installedSkills.failed.map(({ name, reason }) => `${name}:${reason}`)
|
|
3166
|
+
};
|
|
3167
|
+
if (isTTY() && !flags.json) {
|
|
3168
|
+
const total = skillsResult.installed.length;
|
|
3169
|
+
writeStderr(` ${green("✓")} ${total} skills installed to ~/.claude/skills/`);
|
|
3170
|
+
if (skillsResult.skipped.length > 0) {
|
|
3171
|
+
writeStderr(` ${dim(`${skillsResult.skipped.length} skills not yet bundled (skipped)`)}`);
|
|
3172
|
+
}
|
|
2802
3173
|
}
|
|
2803
3174
|
}
|
|
2804
3175
|
} catch (e) {
|
|
@@ -2856,8 +3227,8 @@ ${scraped.url}
|
|
|
2856
3227
|
RESEND_WEBHOOK_SECRET: "https://resend.com/webhooks",
|
|
2857
3228
|
GEMINI_API_KEY: "https://aistudio.google.com/apikey",
|
|
2858
3229
|
FIRECRAWL_API_KEY: "https://firecrawl.dev",
|
|
2859
|
-
MKTG_X_AUTH_TOKEN: "https://github.com/MoizIbnYousaf/mktg
|
|
2860
|
-
MKTG_X_CT0: "https://github.com/MoizIbnYousaf/mktg
|
|
3230
|
+
MKTG_X_AUTH_TOKEN: "https://github.com/MoizIbnYousaf/marketing-cli/blob/main/skills/mktg-x/SKILL.md",
|
|
3231
|
+
MKTG_X_CT0: "https://github.com/MoizIbnYousaf/marketing-cli/blob/main/skills/mktg-x/SKILL.md"
|
|
2861
3232
|
};
|
|
2862
3233
|
const configuredIntegrations = integrationChecks.filter((c) => c.status === "pass").map((c) => c.name.replace("integration-", ""));
|
|
2863
3234
|
const missingIntegrations = integrationChecks.filter((c) => c.status !== "pass").map((c) => {
|
|
@@ -2973,11 +3344,11 @@ var init_init = __esm(() => {
|
|
|
2973
3344
|
});
|
|
2974
3345
|
|
|
2975
3346
|
// src/core/run-log.ts
|
|
2976
|
-
import { join as
|
|
3347
|
+
import { join as join10 } from "node:path";
|
|
2977
3348
|
import { mkdir as mkdir7, appendFile as appendFile2, readFile as readFile3 } from "node:fs/promises";
|
|
2978
3349
|
var LOG_FILE = ".mktg/runs.jsonl", logRun = async (cwd, record) => {
|
|
2979
|
-
const logPath =
|
|
2980
|
-
await mkdir7(
|
|
3350
|
+
const logPath = join10(cwd, LOG_FILE);
|
|
3351
|
+
await mkdir7(join10(cwd, ".mktg"), { recursive: true });
|
|
2981
3352
|
await appendFile2(logPath, JSON.stringify(record) + `
|
|
2982
3353
|
`);
|
|
2983
3354
|
}, parseJsonl = (content) => {
|
|
@@ -2991,7 +3362,7 @@ var LOG_FILE = ".mktg/runs.jsonl", logRun = async (cwd, record) => {
|
|
|
2991
3362
|
}
|
|
2992
3363
|
return records;
|
|
2993
3364
|
}, getRunHistory = async (cwd, skillName, limit = 50) => {
|
|
2994
|
-
const logPath =
|
|
3365
|
+
const logPath = join10(cwd, LOG_FILE);
|
|
2995
3366
|
try {
|
|
2996
3367
|
const content = await readFile3(logPath, "utf-8");
|
|
2997
3368
|
let records = parseJsonl(content);
|
|
@@ -3028,13 +3399,13 @@ __export(exports_status, {
|
|
|
3028
3399
|
schema: () => schema3,
|
|
3029
3400
|
handler: () => handler3
|
|
3030
3401
|
});
|
|
3031
|
-
import { join as
|
|
3402
|
+
import { join as join11 } from "node:path";
|
|
3032
3403
|
var schema3, countContentFiles = async (cwd) => {
|
|
3033
3404
|
const contentDirs = ["marketing", "campaigns", "content"];
|
|
3034
3405
|
const byDir = {};
|
|
3035
3406
|
let totalFiles = 0;
|
|
3036
3407
|
for (const dir of contentDirs) {
|
|
3037
|
-
const dirPath =
|
|
3408
|
+
const dirPath = join11(cwd, dir);
|
|
3038
3409
|
let count = 0;
|
|
3039
3410
|
try {
|
|
3040
3411
|
const glob = new Bun.Glob("**/*.{md,mdx,txt,html}");
|
|
@@ -3049,7 +3420,7 @@ var schema3, countContentFiles = async (cwd) => {
|
|
|
3049
3420
|
return { totalFiles, byDir };
|
|
3050
3421
|
}, getProjectName = async (cwd) => {
|
|
3051
3422
|
try {
|
|
3052
|
-
const pkgPath =
|
|
3423
|
+
const pkgPath = join11(cwd, "package.json");
|
|
3053
3424
|
const file = Bun.file(pkgPath);
|
|
3054
3425
|
if (await file.exists()) {
|
|
3055
3426
|
const pkg = await file.json();
|
|
@@ -3121,7 +3492,7 @@ var schema3, countContentFiles = async (cwd) => {
|
|
|
3121
3492
|
const brandEntries = await Promise.all(brandStatuses.map(async (status) => {
|
|
3122
3493
|
if (!status.exists)
|
|
3123
3494
|
return [status.file, { exists: false, freshness: "missing" }];
|
|
3124
|
-
const filePath =
|
|
3495
|
+
const filePath = join11(cwd, "brand", status.file);
|
|
3125
3496
|
try {
|
|
3126
3497
|
const content = await Bun.file(filePath).text();
|
|
3127
3498
|
const isTemplate = isTemplateContent(status.file, content);
|
|
@@ -3269,7 +3640,7 @@ __export(exports_list, {
|
|
|
3269
3640
|
schema: () => schema4,
|
|
3270
3641
|
handler: () => handler4
|
|
3271
3642
|
});
|
|
3272
|
-
import { join as
|
|
3643
|
+
import { join as join12 } from "node:path";
|
|
3273
3644
|
var schema4, CATEGORY_LABELS, CATEGORY_ORDER, handler4 = async (args, flags) => {
|
|
3274
3645
|
const wantsNdjson = args.includes("--ndjson");
|
|
3275
3646
|
const wantsRouting = args.includes("--routing");
|
|
@@ -3307,7 +3678,7 @@ var schema4, CATEGORY_LABELS, CATEGORY_ORDER, handler4 = async (args, flags) =>
|
|
|
3307
3678
|
source_path: ext.source_path,
|
|
3308
3679
|
triggers: ext.triggers,
|
|
3309
3680
|
added: ext.added,
|
|
3310
|
-
source_exists: await Bun.file(
|
|
3681
|
+
source_exists: await Bun.file(join12(ext.source_path, "SKILL.md")).exists()
|
|
3311
3682
|
})));
|
|
3312
3683
|
const result = {
|
|
3313
3684
|
skills,
|
|
@@ -3527,14 +3898,14 @@ var init_update = __esm(() => {
|
|
|
3527
3898
|
var isKeyOf = (obj, key) => (key in obj);
|
|
3528
3899
|
|
|
3529
3900
|
// src/core/skill-add.ts
|
|
3530
|
-
import { join as
|
|
3901
|
+
import { join as join13 } from "node:path";
|
|
3531
3902
|
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir8 } from "node:fs/promises";
|
|
3532
3903
|
var addExternalSkill = async (opts) => {
|
|
3533
3904
|
const { source, cwd, dryRun, manifest } = opts;
|
|
3534
3905
|
const foundPath = await findExternalSkill(source);
|
|
3535
3906
|
if (!foundPath)
|
|
3536
3907
|
return { kind: "not-found", source };
|
|
3537
|
-
const skillMdPath =
|
|
3908
|
+
const skillMdPath = join13(foundPath, "SKILL.md");
|
|
3538
3909
|
let content;
|
|
3539
3910
|
try {
|
|
3540
3911
|
content = await readFile4(skillMdPath, "utf-8");
|
|
@@ -3552,7 +3923,7 @@ var addExternalSkill = async (opts) => {
|
|
|
3552
3923
|
if (dryRun) {
|
|
3553
3924
|
return { name: fm.name, mode: "chain", source_path: foundPath, action: "dry-run", conflicts };
|
|
3554
3925
|
}
|
|
3555
|
-
const projectManifestPath =
|
|
3926
|
+
const projectManifestPath = join13(cwd, "skills-manifest.json");
|
|
3556
3927
|
let projectManifest;
|
|
3557
3928
|
try {
|
|
3558
3929
|
const raw = await readFile4(projectManifestPath, "utf-8");
|
|
@@ -3588,7 +3959,7 @@ __export(exports_skill, {
|
|
|
3588
3959
|
handler: () => handler6
|
|
3589
3960
|
});
|
|
3590
3961
|
import { readFile as readFile5, stat as stat3 } from "node:fs/promises";
|
|
3591
|
-
import { join as
|
|
3962
|
+
import { join as join14 } from "node:path";
|
|
3592
3963
|
var SUBCOMMANDS, schema6, handleInfo = async (args, flags) => {
|
|
3593
3964
|
const name = args[0];
|
|
3594
3965
|
if (!name)
|
|
@@ -3611,8 +3982,8 @@ var SUBCOMMANDS, schema6, handleInfo = async (args, flags) => {
|
|
|
3611
3982
|
const encodingCheck = detectDoubleEncoding(path);
|
|
3612
3983
|
if (!encodingCheck.ok)
|
|
3613
3984
|
return invalidArgs(encodingCheck.message, ["Use plain paths, not URL-encoded components"]);
|
|
3614
|
-
const fullPath = path.startsWith("/") ? path :
|
|
3615
|
-
const skillMdPath = fullPath.endsWith("SKILL.md") ? fullPath :
|
|
3985
|
+
const fullPath = path.startsWith("/") ? path : join14(flags.cwd, path);
|
|
3986
|
+
const skillMdPath = fullPath.endsWith("SKILL.md") ? fullPath : join14(fullPath, "SKILL.md");
|
|
3616
3987
|
try {
|
|
3617
3988
|
const fileStat = await stat3(skillMdPath);
|
|
3618
3989
|
if (fileStat.size > 256 * 1024) {
|
|
@@ -3654,14 +4025,14 @@ var SUBCOMMANDS, schema6, handleInfo = async (args, flags) => {
|
|
|
3654
4025
|
return invalidArgs(encodingCheck.message, ["Use plain paths, not URL-encoded components"]);
|
|
3655
4026
|
const manifest = await resolveManifest(flags.cwd);
|
|
3656
4027
|
if (flags.dryRun) {
|
|
3657
|
-
const fullPath = path.startsWith("/") ? path :
|
|
3658
|
-
const skillMdPath = fullPath.endsWith("SKILL.md") ? fullPath :
|
|
4028
|
+
const fullPath = path.startsWith("/") ? path : join14(flags.cwd, path);
|
|
4029
|
+
const skillMdPath = fullPath.endsWith("SKILL.md") ? fullPath : join14(fullPath, "SKILL.md");
|
|
3659
4030
|
try {
|
|
3660
4031
|
const content = await readFile5(skillMdPath, "utf-8");
|
|
3661
4032
|
const validated = validateSkill(content, manifest);
|
|
3662
|
-
return ok({ name: validated.valid ? "(valid, would register)" : "(invalid, would fail)", action: "dry-run", manifestPath:
|
|
4033
|
+
return ok({ name: validated.valid ? "(valid, would register)" : "(invalid, would fail)", action: "dry-run", manifestPath: join14(flags.cwd, "skills-manifest.json"), validation: validated });
|
|
3663
4034
|
} catch {
|
|
3664
|
-
return ok({ name: "(dry-run)", action: "dry-run", manifestPath:
|
|
4035
|
+
return ok({ name: "(dry-run)", action: "dry-run", manifestPath: join14(flags.cwd, "skills-manifest.json"), validation: null });
|
|
3665
4036
|
}
|
|
3666
4037
|
}
|
|
3667
4038
|
const result = await registerSkill(path, flags.cwd, manifest);
|
|
@@ -3679,8 +4050,8 @@ var SUBCOMMANDS, schema6, handleInfo = async (args, flags) => {
|
|
|
3679
4050
|
const encodingCheck = detectDoubleEncoding(path);
|
|
3680
4051
|
if (!encodingCheck.ok)
|
|
3681
4052
|
return invalidArgs(encodingCheck.message, ["Use plain paths, not URL-encoded components"]);
|
|
3682
|
-
const fullPath = path.startsWith("/") ? path :
|
|
3683
|
-
const skillMdPath = fullPath.endsWith("SKILL.md") ? fullPath :
|
|
4053
|
+
const fullPath = path.startsWith("/") ? path : join14(flags.cwd, path);
|
|
4054
|
+
const skillMdPath = fullPath.endsWith("SKILL.md") ? fullPath : join14(fullPath, "SKILL.md");
|
|
3684
4055
|
try {
|
|
3685
4056
|
const fileStat = await stat3(skillMdPath);
|
|
3686
4057
|
if (fileStat.size > 256 * 1024) {
|
|
@@ -3702,7 +4073,7 @@ var SUBCOMMANDS, schema6, handleInfo = async (args, flags) => {
|
|
|
3702
4073
|
if (!name)
|
|
3703
4074
|
return invalidArgs("Missing skill name", ["Usage: mktg skill unregister <name> --confirm"]);
|
|
3704
4075
|
if (flags.dryRun) {
|
|
3705
|
-
return ok({ name, action: "would-remove", manifestPath:
|
|
4076
|
+
return ok({ name, action: "would-remove", manifestPath: join14(flags.cwd, "skills-manifest.json") });
|
|
3706
4077
|
}
|
|
3707
4078
|
if (!confirm) {
|
|
3708
4079
|
return invalidArgs(`skill unregister removes '${name}' from the project manifest — pass --confirm to proceed`, [
|
|
@@ -4029,7 +4400,7 @@ __export(exports_brand, {
|
|
|
4029
4400
|
schema: () => schema7,
|
|
4030
4401
|
handler: () => handler7
|
|
4031
4402
|
});
|
|
4032
|
-
import { join as
|
|
4403
|
+
import { join as join15 } from "node:path";
|
|
4033
4404
|
import { mkdir as mkdir9, writeFile as writeFile4, rm, unlink } from "node:fs/promises";
|
|
4034
4405
|
var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
4035
4406
|
const bundle = await exportBrand(flags.cwd);
|
|
@@ -4067,7 +4438,7 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4067
4438
|
"mktg brand import --file bundle.json --dry-run"
|
|
4068
4439
|
]);
|
|
4069
4440
|
}
|
|
4070
|
-
const resolvedPath = filePath.startsWith("/") ? filePath :
|
|
4441
|
+
const resolvedPath = filePath.startsWith("/") ? filePath : join15(flags.cwd, filePath);
|
|
4071
4442
|
const file = Bun.file(resolvedPath);
|
|
4072
4443
|
if (!await file.exists()) {
|
|
4073
4444
|
return notFound(`Bundle file: ${filePath}`, [
|
|
@@ -4149,7 +4520,7 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4149
4520
|
const brandFileSet = new Set(BRAND_FILES);
|
|
4150
4521
|
const written = [];
|
|
4151
4522
|
const rejected = [];
|
|
4152
|
-
const brandDir =
|
|
4523
|
+
const brandDir = join15(flags.cwd, "brand");
|
|
4153
4524
|
if (!flags.dryRun) {
|
|
4154
4525
|
await mkdir9(brandDir, { recursive: true });
|
|
4155
4526
|
}
|
|
@@ -4163,7 +4534,7 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4163
4534
|
continue;
|
|
4164
4535
|
}
|
|
4165
4536
|
if (!flags.dryRun) {
|
|
4166
|
-
await writeFile4(
|
|
4537
|
+
await writeFile4(join15(brandDir, fileName), content);
|
|
4167
4538
|
}
|
|
4168
4539
|
written.push(fileName);
|
|
4169
4540
|
}
|
|
@@ -4224,11 +4595,11 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4224
4595
|
}
|
|
4225
4596
|
return claims;
|
|
4226
4597
|
}, handleClaims = async (_args, flags) => {
|
|
4227
|
-
const brandDir =
|
|
4228
|
-
const landscapePath =
|
|
4598
|
+
const brandDir = join15(flags.cwd, "brand");
|
|
4599
|
+
const landscapePath = join15(brandDir, "landscape.md");
|
|
4229
4600
|
const bunFile = Bun.file(landscapePath);
|
|
4230
4601
|
if (!await bunFile.exists()) {
|
|
4231
|
-
const brandDirFile = Bun.file(
|
|
4602
|
+
const brandDirFile = Bun.file(join15(brandDir, "voice-profile.md"));
|
|
4232
4603
|
if (!await brandDirFile.exists()) {
|
|
4233
4604
|
return notFound("brand/ directory", [
|
|
4234
4605
|
"Run: mktg init",
|
|
@@ -4303,11 +4674,11 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4303
4674
|
}
|
|
4304
4675
|
};
|
|
4305
4676
|
}, KIT_SECTIONS, handleKit = async (args, flags) => {
|
|
4306
|
-
const brandDir =
|
|
4307
|
-
const kitPath =
|
|
4677
|
+
const brandDir = join15(flags.cwd, "brand");
|
|
4678
|
+
const kitPath = join15(brandDir, "creative-kit.md");
|
|
4308
4679
|
const bunFile = Bun.file(kitPath);
|
|
4309
4680
|
if (!await bunFile.exists()) {
|
|
4310
|
-
const brandDirFile = Bun.file(
|
|
4681
|
+
const brandDirFile = Bun.file(join15(brandDir, "voice-profile.md"));
|
|
4311
4682
|
if (!await brandDirFile.exists()) {
|
|
4312
4683
|
return notFound("brand/ directory", [
|
|
4313
4684
|
"Run: mktg init",
|
|
@@ -4399,8 +4770,8 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4399
4770
|
`Valid files: ${BRAND_FILES.join(", ")}`
|
|
4400
4771
|
]);
|
|
4401
4772
|
}
|
|
4402
|
-
const brandDir =
|
|
4403
|
-
const filePath =
|
|
4773
|
+
const brandDir = join15(flags.cwd, "brand");
|
|
4774
|
+
const filePath = join15(brandDir, fileName);
|
|
4404
4775
|
const fileExists = await Bun.file(filePath).exists();
|
|
4405
4776
|
if (!fileExists) {
|
|
4406
4777
|
return notFound(`brand/${fileName}`, [
|
|
@@ -4444,7 +4815,7 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4444
4815
|
"mktg brand reset --confirm --include-learnings (also clears learnings.md entries)"
|
|
4445
4816
|
]);
|
|
4446
4817
|
}
|
|
4447
|
-
const mktgDir =
|
|
4818
|
+
const mktgDir = join15(flags.cwd, ".mktg");
|
|
4448
4819
|
const cleared = [];
|
|
4449
4820
|
const { stat: stat4 } = await import("node:fs/promises");
|
|
4450
4821
|
let mktgDirExists = false;
|
|
@@ -4461,7 +4832,7 @@ var SUBCOMMANDS2, schema7, handleExport = async (_args, flags) => {
|
|
|
4461
4832
|
}
|
|
4462
4833
|
}
|
|
4463
4834
|
if (includeLearnings) {
|
|
4464
|
-
const learningsPath =
|
|
4835
|
+
const learningsPath = join15(flags.cwd, "brand", "learnings.md");
|
|
4465
4836
|
const learningsExists = await Bun.file(learningsPath).exists();
|
|
4466
4837
|
if (learningsExists) {
|
|
4467
4838
|
cleared.push("brand/learnings.md");
|
|
@@ -4575,7 +4946,7 @@ __export(exports_run, {
|
|
|
4575
4946
|
schema: () => schema8,
|
|
4576
4947
|
handler: () => handler8
|
|
4577
4948
|
});
|
|
4578
|
-
import { join as
|
|
4949
|
+
import { join as join16 } from "node:path";
|
|
4579
4950
|
var schema8, handler8 = async (args, flags) => {
|
|
4580
4951
|
const positionalArgs = args.filter((a) => !a.startsWith("--"));
|
|
4581
4952
|
const skillName = positionalArgs[0];
|
|
@@ -4602,7 +4973,7 @@ var schema8, handler8 = async (args, flags) => {
|
|
|
4602
4973
|
}
|
|
4603
4974
|
}));
|
|
4604
4975
|
}
|
|
4605
|
-
const skillMdPath =
|
|
4976
|
+
const skillMdPath = join16(getSkillsInstallDir(), resolved.name, "SKILL.md");
|
|
4606
4977
|
const skillFile = Bun.file(skillMdPath);
|
|
4607
4978
|
if (!await skillFile.exists()) {
|
|
4608
4979
|
return notFound(`Installed skill '${resolved.name}'`, [
|
|
@@ -4717,7 +5088,7 @@ var init_run = __esm(() => {
|
|
|
4717
5088
|
// src/core/transcribe.ts
|
|
4718
5089
|
import { existsSync } from "node:fs";
|
|
4719
5090
|
import { unlink as unlink2 } from "node:fs/promises";
|
|
4720
|
-
import { basename, extname, join as
|
|
5091
|
+
import { basename, extname, join as join17, resolve as resolve2 } from "node:path";
|
|
4721
5092
|
import { homedir as homedir4 } from "node:os";
|
|
4722
5093
|
var WHISPER_MODELS, DEFAULT_MODEL = "small", AUDIO_EXTENSIONS, VIDEO_EXTENSIONS, YOUTUBE_HOSTS, isUrl = (s) => /^https?:\/\//i.test(s), classifyUrl = (url) => {
|
|
4723
5094
|
try {
|
|
@@ -4766,7 +5137,7 @@ var WHISPER_MODELS, DEFAULT_MODEL = "small", AUDIO_EXTENSIONS, VIDEO_EXTENSIONS,
|
|
|
4766
5137
|
return { ok: false, message: `File not found: ${source}` };
|
|
4767
5138
|
}
|
|
4768
5139
|
return { ok: true, detection: { kind: "file", sourceType, path: pathCheck.path } };
|
|
4769
|
-
}, getModelPath = (model) =>
|
|
5140
|
+
}, getModelPath = (model) => join17(homedir4(), ".mktg", "transcribe", "models", `ggml-${model}.bin`), srtTimeToSeconds = (ts) => {
|
|
4770
5141
|
const match = ts.match(/(\d+):(\d+):(\d+)[,.](\d+)/);
|
|
4771
5142
|
if (!match)
|
|
4772
5143
|
return 0;
|
|
@@ -5240,7 +5611,7 @@ __export(exports_context, {
|
|
|
5240
5611
|
schema: () => schema10,
|
|
5241
5612
|
handler: () => handler10
|
|
5242
5613
|
});
|
|
5243
|
-
import { join as
|
|
5614
|
+
import { join as join18 } from "node:path";
|
|
5244
5615
|
import { mkdir as mkdir11 } from "node:fs/promises";
|
|
5245
5616
|
var estimateTokens = (text) => Math.ceil(text.length / 4), truncateToTokens = (content, maxTokens) => {
|
|
5246
5617
|
if (estimateTokens(content) <= maxTokens)
|
|
@@ -5255,7 +5626,7 @@ var estimateTokens = (text) => Math.ceil(text.length / 4), truncateToTokens = (c
|
|
|
5255
5626
|
return { text, truncated: true };
|
|
5256
5627
|
}, VALID_LAYERS, schema10, FILE_PRIORITY, getProjectName2 = async (cwd) => {
|
|
5257
5628
|
try {
|
|
5258
|
-
const file = Bun.file(
|
|
5629
|
+
const file = Bun.file(join18(cwd, "package.json"));
|
|
5259
5630
|
if (await file.exists()) {
|
|
5260
5631
|
const pkg = await file.json();
|
|
5261
5632
|
if (pkg.name)
|
|
@@ -5312,7 +5683,7 @@ var estimateTokens = (text) => Math.ceil(text.length / 4), truncateToTokens = (c
|
|
|
5312
5683
|
const targetFiles = layer ? CONTEXT_MATRIX[layer] : BRAND_FILES;
|
|
5313
5684
|
const brandStatuses = await getBrandStatus(cwd);
|
|
5314
5685
|
const statusMap = new Map(brandStatuses.map((s) => [s.file, s]));
|
|
5315
|
-
const brandDir =
|
|
5686
|
+
const brandDir = join18(cwd, "brand");
|
|
5316
5687
|
const fileEntries = [];
|
|
5317
5688
|
let totalPopulated = 0;
|
|
5318
5689
|
let totalTemplate = 0;
|
|
@@ -5321,7 +5692,7 @@ var estimateTokens = (text) => Math.ceil(text.length / 4), truncateToTokens = (c
|
|
|
5321
5692
|
const status = statusMap.get(file);
|
|
5322
5693
|
if (!status || !status.exists)
|
|
5323
5694
|
continue;
|
|
5324
|
-
const filePath =
|
|
5695
|
+
const filePath = join18(brandDir, file);
|
|
5325
5696
|
try {
|
|
5326
5697
|
const content = await Bun.file(filePath).text();
|
|
5327
5698
|
const isTemplate = isTemplateContent(file, content);
|
|
@@ -5383,9 +5754,9 @@ var estimateTokens = (text) => Math.ceil(text.length / 4), truncateToTokens = (c
|
|
|
5383
5754
|
if (flags.dryRun) {
|
|
5384
5755
|
writeStderr("dry-run: would write .mktg/context.json");
|
|
5385
5756
|
} else {
|
|
5386
|
-
const mktgDir =
|
|
5757
|
+
const mktgDir = join18(cwd, ".mktg");
|
|
5387
5758
|
await mkdir11(mktgDir, { recursive: true });
|
|
5388
|
-
await Bun.write(
|
|
5759
|
+
await Bun.write(join18(mktgDir, "context.json"), JSON.stringify(result, null, 2));
|
|
5389
5760
|
writeStderr("Saved to .mktg/context.json");
|
|
5390
5761
|
}
|
|
5391
5762
|
}
|
|
@@ -5466,10 +5837,10 @@ __export(exports_plan, {
|
|
|
5466
5837
|
schema: () => schema11,
|
|
5467
5838
|
handler: () => handler11
|
|
5468
5839
|
});
|
|
5469
|
-
import { join as
|
|
5840
|
+
import { join as join19 } from "node:path";
|
|
5470
5841
|
import { mkdir as mkdir12 } from "node:fs/promises";
|
|
5471
5842
|
var schema11, PLAN_FILE = ".mktg/plan.json", loadPersisted = async (cwd) => {
|
|
5472
|
-
const file = Bun.file(
|
|
5843
|
+
const file = Bun.file(join19(cwd, PLAN_FILE));
|
|
5473
5844
|
if (!await file.exists())
|
|
5474
5845
|
return null;
|
|
5475
5846
|
try {
|
|
@@ -5478,8 +5849,8 @@ var schema11, PLAN_FILE = ".mktg/plan.json", loadPersisted = async (cwd) => {
|
|
|
5478
5849
|
return null;
|
|
5479
5850
|
}
|
|
5480
5851
|
}, savePersisted = async (cwd, plan) => {
|
|
5481
|
-
await mkdir12(
|
|
5482
|
-
await Bun.write(
|
|
5852
|
+
await mkdir12(join19(cwd, ".mktg"), { recursive: true });
|
|
5853
|
+
await Bun.write(join19(cwd, PLAN_FILE), JSON.stringify(plan, null, 2));
|
|
5483
5854
|
}, buildTasks = async (cwd, runSummary, completed, ndjson = false) => {
|
|
5484
5855
|
const tasks = [];
|
|
5485
5856
|
const completedSet = new Set(completed);
|
|
@@ -5520,7 +5891,7 @@ var schema11, PLAN_FILE = ".mktg/plan.json", loadPersisted = async (cwd) => {
|
|
|
5520
5891
|
continue;
|
|
5521
5892
|
}
|
|
5522
5893
|
try {
|
|
5523
|
-
const content = await Bun.file(
|
|
5894
|
+
const content = await Bun.file(join19(cwd, "brand", file)).text();
|
|
5524
5895
|
if (isTemplateContent(file, content)) {
|
|
5525
5896
|
const skillMap = {
|
|
5526
5897
|
"voice-profile.md": "brand-voice",
|
|
@@ -5536,7 +5907,7 @@ var schema11, PLAN_FILE = ".mktg/plan.json", loadPersisted = async (cwd) => {
|
|
|
5536
5907
|
const needsVoice = file !== "voice-profile.md" && foundationOrder.includes(file);
|
|
5537
5908
|
const voicePopulated = !brandStatuses.find((s) => s.file === "voice-profile.md")?.exists ? false : !await (async () => {
|
|
5538
5909
|
try {
|
|
5539
|
-
const vc = await Bun.file(
|
|
5910
|
+
const vc = await Bun.file(join19(cwd, "brand", "voice-profile.md")).text();
|
|
5540
5911
|
return isTemplateContent("voice-profile.md", vc);
|
|
5541
5912
|
} catch {
|
|
5542
5913
|
return true;
|
|
@@ -5725,10 +6096,17 @@ var init_plan = __esm(() => {
|
|
|
5725
6096
|
// src/commands/publish.ts
|
|
5726
6097
|
var exports_publish = {};
|
|
5727
6098
|
__export(exports_publish, {
|
|
6099
|
+
sentMarkerKey: () => sentMarkerKey,
|
|
5728
6100
|
schema: () => schema12,
|
|
5729
|
-
|
|
6101
|
+
postizFetch: () => postizFetch,
|
|
6102
|
+
persistSentMarker: () => persistSentMarker,
|
|
6103
|
+
loadSentMarker: () => loadSentMarker,
|
|
6104
|
+
handler: () => handler12,
|
|
6105
|
+
BUILTIN_PUBLISH_ADAPTERS: () => BUILTIN_PUBLISH_ADAPTERS
|
|
5730
6106
|
});
|
|
5731
|
-
import { join as
|
|
6107
|
+
import { join as join20 } from "node:path";
|
|
6108
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6109
|
+
import { mkdir as mkdir13, rename, writeFile as writeFile5 } from "node:fs/promises";
|
|
5732
6110
|
var schema12, publishTypefully = async (items, confirm, ndjson) => {
|
|
5733
6111
|
const apiKey = process.env.TYPEFULLY_API_KEY;
|
|
5734
6112
|
const results = [];
|
|
@@ -5831,7 +6209,7 @@ var schema12, publishTypefully = async (items, confirm, ndjson) => {
|
|
|
5831
6209
|
};
|
|
5832
6210
|
}, publishFile = async (items, confirm, cwd, ndjson) => {
|
|
5833
6211
|
const results = [];
|
|
5834
|
-
const outDir =
|
|
6212
|
+
const outDir = join20(cwd, ".mktg", "published");
|
|
5835
6213
|
for (let i = 0;i < items.length; i++) {
|
|
5836
6214
|
const item = items[i];
|
|
5837
6215
|
const rawFilename = item.metadata?.filename ?? `item-${i}.txt`;
|
|
@@ -5845,7 +6223,7 @@ var schema12, publishTypefully = async (items, confirm, ndjson) => {
|
|
|
5845
6223
|
try {
|
|
5846
6224
|
const { mkdir: mkdirFs } = await import("node:fs/promises");
|
|
5847
6225
|
await mkdirFs(outDir, { recursive: true });
|
|
5848
|
-
await Bun.write(
|
|
6226
|
+
await Bun.write(join20(outDir, filename), item.content);
|
|
5849
6227
|
results.push({ item: i, status: "published", detail: `Written to ${filename}` });
|
|
5850
6228
|
} catch (e) {
|
|
5851
6229
|
results.push({ item: i, status: "failed", detail: e instanceof Error ? e.message : "Unknown error" });
|
|
@@ -5861,7 +6239,269 @@ var schema12, publishTypefully = async (items, confirm, ndjson) => {
|
|
|
5861
6239
|
errors: results.filter((r) => r.status === "failed").map((r) => r.detail),
|
|
5862
6240
|
results
|
|
5863
6241
|
};
|
|
5864
|
-
},
|
|
6242
|
+
}, POSTIZ_DEFAULT_BASE = "https://api.postiz.com", postizFetch = async (path, init) => {
|
|
6243
|
+
const apiKey = process.env.POSTIZ_API_KEY;
|
|
6244
|
+
const base = process.env.POSTIZ_API_BASE ?? POSTIZ_DEFAULT_BASE;
|
|
6245
|
+
if (!apiKey) {
|
|
6246
|
+
return { ok: false, error: { kind: "auth-missing" }, status: null };
|
|
6247
|
+
}
|
|
6248
|
+
const headers = {
|
|
6249
|
+
...init.headers ?? {},
|
|
6250
|
+
Authorization: apiKey
|
|
6251
|
+
};
|
|
6252
|
+
let body;
|
|
6253
|
+
if (init.body instanceof FormData) {
|
|
6254
|
+
body = init.body;
|
|
6255
|
+
} else if (init.body !== undefined) {
|
|
6256
|
+
body = JSON.stringify(init.body);
|
|
6257
|
+
headers["Content-Type"] = "application/json";
|
|
6258
|
+
}
|
|
6259
|
+
let res;
|
|
6260
|
+
try {
|
|
6261
|
+
const fetchInit = {
|
|
6262
|
+
method: init.method,
|
|
6263
|
+
headers,
|
|
6264
|
+
signal: AbortSignal.timeout(15000),
|
|
6265
|
+
...body !== undefined ? { body } : {}
|
|
6266
|
+
};
|
|
6267
|
+
res = await fetch(`${base}${path}`, fetchInit);
|
|
6268
|
+
} catch (e) {
|
|
6269
|
+
return {
|
|
6270
|
+
ok: false,
|
|
6271
|
+
error: { kind: "network", detail: e instanceof Error ? e.message : String(e) },
|
|
6272
|
+
status: null
|
|
6273
|
+
};
|
|
6274
|
+
}
|
|
6275
|
+
if (res.ok) {
|
|
6276
|
+
const data = await res.json().catch(() => ({}));
|
|
6277
|
+
return { ok: true, data, status: res.status };
|
|
6278
|
+
}
|
|
6279
|
+
const errBody = await res.json().catch(() => ({}));
|
|
6280
|
+
const msg = typeof errBody.msg === "string" ? errBody.msg : `HTTP ${res.status}`;
|
|
6281
|
+
if (res.status === 401) {
|
|
6282
|
+
if (msg === "No subscription found") {
|
|
6283
|
+
return { ok: false, error: { kind: "subscription-required", msg }, status: 401 };
|
|
6284
|
+
}
|
|
6285
|
+
return { ok: false, error: { kind: "auth-invalid", msg }, status: 401 };
|
|
6286
|
+
}
|
|
6287
|
+
if (res.status === 429) {
|
|
6288
|
+
const retryAfter = res.headers.get("retry-after");
|
|
6289
|
+
const retryAfterSeconds = retryAfter && /^\d+$/.test(retryAfter) ? Number(retryAfter) : null;
|
|
6290
|
+
return { ok: false, error: { kind: "rate-limited", retryAfterSeconds, msg }, status: 429 };
|
|
6291
|
+
}
|
|
6292
|
+
if (res.status >= 400 && res.status < 500) {
|
|
6293
|
+
return { ok: false, error: { kind: "bad-request", msg, status: res.status }, status: res.status };
|
|
6294
|
+
}
|
|
6295
|
+
return { ok: false, error: { kind: "server-error", status: res.status, msg }, status: res.status };
|
|
6296
|
+
}, mapPostizError = (e) => {
|
|
6297
|
+
switch (e.kind) {
|
|
6298
|
+
case "auth-missing":
|
|
6299
|
+
return "POSTIZ_API_KEY is not set. Run: mktg catalog info postiz";
|
|
6300
|
+
case "auth-invalid":
|
|
6301
|
+
return `Invalid POSTIZ_API_KEY (${e.msg}). Verify the key in the postiz UI → Settings → API.`;
|
|
6302
|
+
case "subscription-required":
|
|
6303
|
+
return `Hosted postiz requires an active subscription (${e.msg}). Upgrade at https://postiz.com/pricing, or self-host (mktg catalog info postiz).`;
|
|
6304
|
+
case "rate-limited":
|
|
6305
|
+
return e.retryAfterSeconds !== null ? `Postiz rate limit (30 posts/hour per org) — retry in ${e.retryAfterSeconds}s. On self-host, raise API_LIMIT env var.` : "Postiz rate limit (30 posts/hour per org). Retry later or raise API_LIMIT on self-host.";
|
|
6306
|
+
case "bad-request":
|
|
6307
|
+
return `Postiz rejected request (HTTP ${e.status}): ${e.msg}. Check CreatePostDto body shape — see docs/integration/postiz-api-reference.md §4.`;
|
|
6308
|
+
case "server-error":
|
|
6309
|
+
return `Postiz server error (HTTP ${e.status}): ${e.msg}. Retry; if persistent, check postiz health at POSTIZ_API_BASE.`;
|
|
6310
|
+
case "network":
|
|
6311
|
+
return `Network error contacting postiz: ${e.detail}. Verify POSTIZ_API_BASE and connectivity.`;
|
|
6312
|
+
}
|
|
6313
|
+
}, sentMarkerKey = (campaign, content, integrationIds) => {
|
|
6314
|
+
const ids = [...integrationIds].sort().join("|");
|
|
6315
|
+
const buf = `${campaign}||${content}||${ids}`;
|
|
6316
|
+
return createHash2("sha256").update(buf).digest("hex");
|
|
6317
|
+
}, emptySentMarker = (campaign) => ({
|
|
6318
|
+
version: 1,
|
|
6319
|
+
campaign,
|
|
6320
|
+
catalog: "postiz",
|
|
6321
|
+
sent: {}
|
|
6322
|
+
}), isPostizSentMarker = (v) => {
|
|
6323
|
+
if (!v || typeof v !== "object")
|
|
6324
|
+
return false;
|
|
6325
|
+
const m = v;
|
|
6326
|
+
if (m.version !== 1)
|
|
6327
|
+
return false;
|
|
6328
|
+
if (typeof m.campaign !== "string")
|
|
6329
|
+
return false;
|
|
6330
|
+
if (m.catalog !== "postiz")
|
|
6331
|
+
return false;
|
|
6332
|
+
if (!m.sent || typeof m.sent !== "object" || Array.isArray(m.sent))
|
|
6333
|
+
return false;
|
|
6334
|
+
for (const entry of Object.values(m.sent)) {
|
|
6335
|
+
if (!entry || typeof entry !== "object")
|
|
6336
|
+
return false;
|
|
6337
|
+
const e = entry;
|
|
6338
|
+
if (typeof e.postedAt !== "string")
|
|
6339
|
+
return false;
|
|
6340
|
+
if (!Array.isArray(e.providers) || !e.providers.every((p) => typeof p === "string"))
|
|
6341
|
+
return false;
|
|
6342
|
+
}
|
|
6343
|
+
return true;
|
|
6344
|
+
}, archiveCorrupt = async (path) => {
|
|
6345
|
+
const iso = new Date().toISOString().replace(/:/g, "-");
|
|
6346
|
+
const corruptPath = path.replace(/\.json$/, `.corrupt.${iso}.json`);
|
|
6347
|
+
try {
|
|
6348
|
+
await rename(path, corruptPath);
|
|
6349
|
+
} catch {}
|
|
6350
|
+
}, loadSentMarker = async (path, campaign) => {
|
|
6351
|
+
try {
|
|
6352
|
+
const file = Bun.file(path);
|
|
6353
|
+
if (!await file.exists())
|
|
6354
|
+
return emptySentMarker(campaign);
|
|
6355
|
+
const raw = await file.text();
|
|
6356
|
+
const parsed = JSON.parse(raw);
|
|
6357
|
+
if (!isPostizSentMarker(parsed)) {
|
|
6358
|
+
await archiveCorrupt(path);
|
|
6359
|
+
writeStderr(JSON.stringify({ type: "postiz-sent-marker-corrupt", path, detail: "shape mismatch" }));
|
|
6360
|
+
return emptySentMarker(campaign);
|
|
6361
|
+
}
|
|
6362
|
+
if (parsed.campaign !== campaign)
|
|
6363
|
+
return emptySentMarker(campaign);
|
|
6364
|
+
return parsed;
|
|
6365
|
+
} catch (e) {
|
|
6366
|
+
await archiveCorrupt(path);
|
|
6367
|
+
writeStderr(JSON.stringify({ type: "postiz-sent-marker-corrupt", path, detail: e instanceof Error ? e.message : String(e) }));
|
|
6368
|
+
return emptySentMarker(campaign);
|
|
6369
|
+
}
|
|
6370
|
+
}, persistSentMarker = async (path, marker) => {
|
|
6371
|
+
await mkdir13(join20(path, ".."), { recursive: true });
|
|
6372
|
+
const tmp = `${path}.tmp`;
|
|
6373
|
+
await writeFile5(tmp, JSON.stringify(marker, null, 2));
|
|
6374
|
+
await rename(tmp, path);
|
|
6375
|
+
}, buildCreatePostDraft = (content, resolved) => ({
|
|
6376
|
+
type: "draft",
|
|
6377
|
+
shortLink: false,
|
|
6378
|
+
date: new Date().toISOString(),
|
|
6379
|
+
tags: [],
|
|
6380
|
+
posts: resolved.map(({ id }) => ({
|
|
6381
|
+
integration: { id },
|
|
6382
|
+
value: [{ content, image: [] }]
|
|
6383
|
+
}))
|
|
6384
|
+
}), extractProviders = (item) => {
|
|
6385
|
+
const meta = item.metadata;
|
|
6386
|
+
if (!meta || typeof meta !== "object") {
|
|
6387
|
+
return { ok: false, detail: 'Missing item.metadata.providers[] — add at least one postiz identifier (e.g., "linkedin", "bluesky")' };
|
|
6388
|
+
}
|
|
6389
|
+
const providers = meta.providers;
|
|
6390
|
+
if (!Array.isArray(providers) || providers.length === 0) {
|
|
6391
|
+
return { ok: false, detail: 'Missing item.metadata.providers[] — add at least one postiz identifier (e.g., "linkedin", "bluesky")' };
|
|
6392
|
+
}
|
|
6393
|
+
if (!providers.every((p) => typeof p === "string")) {
|
|
6394
|
+
return { ok: false, detail: "item.metadata.providers[] must contain only strings (postiz identifiers)" };
|
|
6395
|
+
}
|
|
6396
|
+
return { ok: true, providers };
|
|
6397
|
+
}, publishPostiz = async (inp) => {
|
|
6398
|
+
const results = [];
|
|
6399
|
+
const buildFailAll = (detail) => ({
|
|
6400
|
+
adapter: "postiz",
|
|
6401
|
+
items: inp.items.length,
|
|
6402
|
+
published: 0,
|
|
6403
|
+
failed: inp.items.length,
|
|
6404
|
+
errors: Array(inp.items.length).fill(detail),
|
|
6405
|
+
results: inp.items.map((_, i) => ({ item: i, status: "failed", detail }))
|
|
6406
|
+
});
|
|
6407
|
+
const markerPath = join20(inp.cwd, ".mktg", "publish", `${inp.campaign}-postiz.json`);
|
|
6408
|
+
const marker = await loadSentMarker(markerPath, inp.campaign);
|
|
6409
|
+
const listRes = await postizFetch("/public/v1/integrations", { method: "GET" });
|
|
6410
|
+
if (!listRes.ok) {
|
|
6411
|
+
return buildFailAll(mapPostizError(listRes.error));
|
|
6412
|
+
}
|
|
6413
|
+
const identifierToId = new Map;
|
|
6414
|
+
for (const int of listRes.data) {
|
|
6415
|
+
if (!int.disabled)
|
|
6416
|
+
identifierToId.set(int.identifier, int.id);
|
|
6417
|
+
}
|
|
6418
|
+
let published = 0;
|
|
6419
|
+
let failed = 0;
|
|
6420
|
+
let hardStop = false;
|
|
6421
|
+
for (let i = 0;i < inp.items.length; i++) {
|
|
6422
|
+
if (hardStop) {
|
|
6423
|
+
results.push({ item: i, status: "skipped", detail: "Skipped — prior item failed with hard-stop error" });
|
|
6424
|
+
continue;
|
|
6425
|
+
}
|
|
6426
|
+
const item = inp.items[i];
|
|
6427
|
+
const extracted = extractProviders(item);
|
|
6428
|
+
if (!extracted.ok) {
|
|
6429
|
+
results.push({ item: i, status: "failed", detail: extracted.detail });
|
|
6430
|
+
failed++;
|
|
6431
|
+
continue;
|
|
6432
|
+
}
|
|
6433
|
+
const providers = extracted.providers;
|
|
6434
|
+
const resolved = [];
|
|
6435
|
+
const unconnected = [];
|
|
6436
|
+
for (const provider of providers) {
|
|
6437
|
+
const id = identifierToId.get(provider);
|
|
6438
|
+
if (id)
|
|
6439
|
+
resolved.push({ provider, id });
|
|
6440
|
+
else
|
|
6441
|
+
unconnected.push(provider);
|
|
6442
|
+
}
|
|
6443
|
+
if (unconnected.length > 0) {
|
|
6444
|
+
const connected = Array.from(identifierToId.keys()).sort();
|
|
6445
|
+
results.push({
|
|
6446
|
+
item: i,
|
|
6447
|
+
status: "failed",
|
|
6448
|
+
detail: `Unconnected provider(s): ${unconnected.join(", ")}. Connected: ${connected.join(", ") || "(none)"}. Connect in the postiz UI first.`
|
|
6449
|
+
});
|
|
6450
|
+
failed++;
|
|
6451
|
+
continue;
|
|
6452
|
+
}
|
|
6453
|
+
if (!inp.confirm) {
|
|
6454
|
+
results.push({ item: i, status: "skipped", detail: `[dry-run] would draft to: ${providers.join(", ")}` });
|
|
6455
|
+
if (inp.ndjson)
|
|
6456
|
+
writeStderr(JSON.stringify({ adapter: "postiz", item: i, status: "skipped" }));
|
|
6457
|
+
continue;
|
|
6458
|
+
}
|
|
6459
|
+
const key = sentMarkerKey(inp.campaign, item.content, resolved.map((r) => r.id));
|
|
6460
|
+
if (marker.sent[key]) {
|
|
6461
|
+
results.push({ item: i, status: "skipped", detail: "already-sent (sent-marker hit)" });
|
|
6462
|
+
if (inp.ndjson)
|
|
6463
|
+
writeStderr(JSON.stringify({ adapter: "postiz", item: i, status: "skipped", reason: "already-sent" }));
|
|
6464
|
+
continue;
|
|
6465
|
+
}
|
|
6466
|
+
const body = buildCreatePostDraft(item.content, resolved);
|
|
6467
|
+
const postRes = await postizFetch("/public/v1/posts", { method: "POST", body });
|
|
6468
|
+
if (!postRes.ok) {
|
|
6469
|
+
const detail = mapPostizError(postRes.error);
|
|
6470
|
+
results.push({ item: i, status: "failed", detail });
|
|
6471
|
+
failed++;
|
|
6472
|
+
if (inp.ndjson)
|
|
6473
|
+
writeStderr(JSON.stringify({ adapter: "postiz", item: i, status: "failed", detail }));
|
|
6474
|
+
if (postRes.error.kind === "rate-limited" || postRes.error.kind === "subscription-required" || postRes.error.kind === "auth-missing" || postRes.error.kind === "auth-invalid") {
|
|
6475
|
+
hardStop = true;
|
|
6476
|
+
}
|
|
6477
|
+
continue;
|
|
6478
|
+
}
|
|
6479
|
+
marker.sent[key] = { postedAt: new Date().toISOString(), providers: [...providers] };
|
|
6480
|
+
if (inp.ndjson)
|
|
6481
|
+
writeStderr(JSON.stringify({ adapter: "postiz", item: i, status: "published", providers }));
|
|
6482
|
+
results.push({ item: i, status: "published", detail: `draft → ${providers.join(", ")}` });
|
|
6483
|
+
published++;
|
|
6484
|
+
}
|
|
6485
|
+
await persistSentMarker(markerPath, marker).catch((e) => {
|
|
6486
|
+
writeStderr(JSON.stringify({ type: "postiz-sent-marker-write-failed", path: markerPath, detail: e instanceof Error ? e.message : String(e) }));
|
|
6487
|
+
});
|
|
6488
|
+
return {
|
|
6489
|
+
adapter: "postiz",
|
|
6490
|
+
items: inp.items.length,
|
|
6491
|
+
published,
|
|
6492
|
+
failed,
|
|
6493
|
+
errors: results.filter((r) => r.status === "failed").map((r) => r.detail),
|
|
6494
|
+
results
|
|
6495
|
+
};
|
|
6496
|
+
}, listPostizIntegrations = async () => {
|
|
6497
|
+
const res = await postizFetch("/public/v1/integrations", { method: "GET" });
|
|
6498
|
+
if (!res.ok) {
|
|
6499
|
+
const detail = mapPostizError(res.error);
|
|
6500
|
+
const exitCode = res.error.kind === "auth-missing" || res.error.kind === "auth-invalid" || res.error.kind === "subscription-required" ? 3 : res.error.kind === "rate-limited" || res.error.kind === "network" || res.error.kind === "server-error" ? 5 : 2;
|
|
6501
|
+
return { ok: false, detail, exitCode };
|
|
6502
|
+
}
|
|
6503
|
+
return { ok: true, data: { adapter: "postiz", integrations: res.data } };
|
|
6504
|
+
}, BUILTIN_PUBLISH_ADAPTERS, ADAPTERS, ADAPTER_ENV_VARS, handler12 = async (args, flags) => {
|
|
5865
6505
|
const confirm = args.includes("--confirm");
|
|
5866
6506
|
const ndjson = args.includes("--ndjson");
|
|
5867
6507
|
const isDryRun = flags.dryRun || !confirm;
|
|
@@ -5886,12 +6526,35 @@ var schema12, publishTypefully = async (items, confirm, ndjson) => {
|
|
|
5886
6526
|
break;
|
|
5887
6527
|
}
|
|
5888
6528
|
}
|
|
6529
|
+
if (args.includes("--list-integrations")) {
|
|
6530
|
+
if (!adapterFilter) {
|
|
6531
|
+
return err("INVALID_ARGS", "--list-integrations requires --adapter <name>", [
|
|
6532
|
+
"Try: mktg publish --adapter postiz --list-integrations --json"
|
|
6533
|
+
], 2);
|
|
6534
|
+
}
|
|
6535
|
+
if (adapterFilter !== "postiz") {
|
|
6536
|
+
return err("NOT_IMPLEMENTED", `Adapter '${adapterFilter}' does not expose integrations.`, [
|
|
6537
|
+
"This flag is only supported by adapters that back their own provider registry.",
|
|
6538
|
+
"Try --adapter postiz."
|
|
6539
|
+
], 6);
|
|
6540
|
+
}
|
|
6541
|
+
const res = await listPostizIntegrations();
|
|
6542
|
+
if (!res.ok) {
|
|
6543
|
+
const suggestions = [
|
|
6544
|
+
"Verify POSTIZ_API_KEY and POSTIZ_API_BASE (defaults to https://api.postiz.com)",
|
|
6545
|
+
"Run: mktg catalog info postiz --json"
|
|
6546
|
+
];
|
|
6547
|
+
const code = res.exitCode === 3 ? "POSTIZ_AUTH" : res.exitCode === 5 ? "POSTIZ_NETWORK" : "POSTIZ_BAD_REQUEST";
|
|
6548
|
+
return err(code, res.detail, suggestions, res.exitCode);
|
|
6549
|
+
}
|
|
6550
|
+
return ok(res.data);
|
|
6551
|
+
}
|
|
5889
6552
|
const positionalArgs = args.filter((a, i) => !a.startsWith("--") && !flagValues.has(i));
|
|
5890
6553
|
const campaignPath = positionalArgs[0] ?? ".";
|
|
5891
6554
|
const pathCheck = validatePathInput(flags.cwd, campaignPath);
|
|
5892
6555
|
if (!pathCheck.ok)
|
|
5893
6556
|
return err("INVALID_ARGS", pathCheck.message, [], 2);
|
|
5894
|
-
const manifestPath = campaignPath.endsWith("publish.json") ?
|
|
6557
|
+
const manifestPath = campaignPath.endsWith("publish.json") ? join20(flags.cwd, campaignPath) : join20(flags.cwd, campaignPath, "publish.json");
|
|
5895
6558
|
const manifestFile = Bun.file(manifestPath);
|
|
5896
6559
|
let manifest;
|
|
5897
6560
|
if (await manifestFile.exists()) {
|
|
@@ -5933,12 +6596,13 @@ var schema12, publishTypefully = async (items, confirm, ndjson) => {
|
|
|
5933
6596
|
existing.push(item);
|
|
5934
6597
|
grouped.set(adapter, existing);
|
|
5935
6598
|
}
|
|
6599
|
+
const campaignName = manifest.name ?? "unnamed";
|
|
5936
6600
|
const adapterResults = [];
|
|
5937
6601
|
for (const [adapter, items] of grouped) {
|
|
5938
6602
|
const fn = ADAPTERS[adapter];
|
|
5939
6603
|
if (!fn)
|
|
5940
6604
|
continue;
|
|
5941
|
-
const result2 = await fn(items, confirm && !flags.dryRun, flags.cwd, ndjson);
|
|
6605
|
+
const result2 = await fn(items, confirm && !flags.dryRun, flags.cwd, ndjson, campaignName);
|
|
5942
6606
|
adapterResults.push(result2);
|
|
5943
6607
|
}
|
|
5944
6608
|
const totalItems = adapterResults.reduce((sum, r) => sum + r.items, 0);
|
|
@@ -5981,9 +6645,10 @@ var init_publish = __esm(() => {
|
|
|
5981
6645
|
positional: { name: "path", description: "Campaign directory or publish.json path", required: false },
|
|
5982
6646
|
flags: [
|
|
5983
6647
|
{ name: "--confirm", type: "boolean", required: false, description: "Execute publishing (without this, publish is dry-run by default)" },
|
|
5984
|
-
{ name: "--adapter", type: "string", required: false, description: "Run only a specific adapter (typefully, resend, file)" },
|
|
6648
|
+
{ name: "--adapter", type: "string", required: false, description: "Run only a specific adapter (typefully, resend, file, postiz)" },
|
|
5985
6649
|
{ name: "--ndjson", type: "boolean", required: false, description: "Stream progress as NDJSON lines" },
|
|
5986
|
-
{ name: "--list-adapters", type: "boolean", required: false, description: "List available adapters with env var requirements and configured status" }
|
|
6650
|
+
{ name: "--list-adapters", type: "boolean", required: false, description: "List available adapters with env var requirements and configured status" },
|
|
6651
|
+
{ name: "--list-integrations", type: "boolean", required: false, description: "For adapters backed by a provider registry (postiz), list connected providers. Returns NOT_IMPLEMENTED for adapters without one." }
|
|
5987
6652
|
],
|
|
5988
6653
|
output: {
|
|
5989
6654
|
campaign: "string — campaign name from manifest",
|
|
@@ -6002,15 +6667,18 @@ var init_publish = __esm(() => {
|
|
|
6002
6667
|
],
|
|
6003
6668
|
vocabulary: ["publish", "distribute", "push", "ship", "deploy content"]
|
|
6004
6669
|
};
|
|
6670
|
+
BUILTIN_PUBLISH_ADAPTERS = ["typefully", "resend", "file"];
|
|
6005
6671
|
ADAPTERS = {
|
|
6006
|
-
typefully: (items, confirm, _cwd, ndjson) => publishTypefully(items, confirm, ndjson),
|
|
6007
|
-
resend: (items, confirm, _cwd, ndjson) => publishResend(items, confirm, ndjson),
|
|
6008
|
-
file: publishFile
|
|
6672
|
+
typefully: (items, confirm, _cwd, ndjson, _campaign) => publishTypefully(items, confirm, ndjson),
|
|
6673
|
+
resend: (items, confirm, _cwd, ndjson, _campaign) => publishResend(items, confirm, ndjson),
|
|
6674
|
+
file: (items, confirm, cwd, ndjson, _campaign) => publishFile(items, confirm, cwd, ndjson),
|
|
6675
|
+
postiz: (items, confirm, cwd, ndjson, campaign) => publishPostiz({ items, confirm, cwd, ndjson, campaign })
|
|
6009
6676
|
};
|
|
6010
6677
|
ADAPTER_ENV_VARS = {
|
|
6011
6678
|
typefully: "TYPEFULLY_API_KEY",
|
|
6012
6679
|
resend: "RESEND_API_KEY",
|
|
6013
|
-
file: null
|
|
6680
|
+
file: null,
|
|
6681
|
+
postiz: "POSTIZ_API_KEY"
|
|
6014
6682
|
};
|
|
6015
6683
|
});
|
|
6016
6684
|
|
|
@@ -6020,10 +6688,10 @@ __export(exports_compete, {
|
|
|
6020
6688
|
schema: () => schema13,
|
|
6021
6689
|
handler: () => handler13
|
|
6022
6690
|
});
|
|
6023
|
-
import { join as
|
|
6024
|
-
import { mkdir as
|
|
6025
|
-
var schema13, COMPETE_DIR = ".mktg/compete", WATCHLIST_FILE = "watchlist.json", COMPETE_ROUTING, getCompeteDir = (cwd) =>
|
|
6026
|
-
const file = Bun.file(
|
|
6691
|
+
import { join as join21 } from "node:path";
|
|
6692
|
+
import { mkdir as mkdir14 } from "node:fs/promises";
|
|
6693
|
+
var schema13, COMPETE_DIR = ".mktg/compete", WATCHLIST_FILE = "watchlist.json", COMPETE_ROUTING, getCompeteDir = (cwd) => join21(cwd, COMPETE_DIR), loadWatchlist = async (cwd) => {
|
|
6694
|
+
const file = Bun.file(join21(getCompeteDir(cwd), WATCHLIST_FILE));
|
|
6027
6695
|
if (!await file.exists())
|
|
6028
6696
|
return { version: 1, entries: [] };
|
|
6029
6697
|
try {
|
|
@@ -6032,12 +6700,12 @@ var schema13, COMPETE_DIR = ".mktg/compete", WATCHLIST_FILE = "watchlist.json",
|
|
|
6032
6700
|
return { version: 1, entries: [] };
|
|
6033
6701
|
}
|
|
6034
6702
|
}, saveWatchlist = async (cwd, list) => {
|
|
6035
|
-
await
|
|
6036
|
-
await Bun.write(
|
|
6703
|
+
await mkdir14(getCompeteDir(cwd), { recursive: true });
|
|
6704
|
+
await Bun.write(join21(getCompeteDir(cwd), WATCHLIST_FILE), JSON.stringify(list, null, 2));
|
|
6037
6705
|
}, urlToFilename = (url) => {
|
|
6038
6706
|
return url.replace(/https?:\/\//, "").replace(/[^a-z0-9.-]/gi, "_").slice(0, 100);
|
|
6039
6707
|
}, loadSnapshot = async (cwd, url) => {
|
|
6040
|
-
const file = Bun.file(
|
|
6708
|
+
const file = Bun.file(join21(getCompeteDir(cwd), `${urlToFilename(url)}.json`));
|
|
6041
6709
|
if (!await file.exists())
|
|
6042
6710
|
return null;
|
|
6043
6711
|
try {
|
|
@@ -6046,8 +6714,8 @@ var schema13, COMPETE_DIR = ".mktg/compete", WATCHLIST_FILE = "watchlist.json",
|
|
|
6046
6714
|
return null;
|
|
6047
6715
|
}
|
|
6048
6716
|
}, saveSnapshot = async (cwd, snapshot) => {
|
|
6049
|
-
await
|
|
6050
|
-
await Bun.write(
|
|
6717
|
+
await mkdir14(getCompeteDir(cwd), { recursive: true });
|
|
6718
|
+
await Bun.write(join21(getCompeteDir(cwd), `${urlToFilename(snapshot.url)}.json`), JSON.stringify(snapshot, null, 2));
|
|
6051
6719
|
}, fetchPage = async (url) => {
|
|
6052
6720
|
const validated = validatePublicUrl(url);
|
|
6053
6721
|
if (!validated.ok) {
|
|
@@ -6352,9 +7020,9 @@ __export(exports_dashboard, {
|
|
|
6352
7020
|
});
|
|
6353
7021
|
import { randomUUID } from "node:crypto";
|
|
6354
7022
|
import { existsSync as existsSync3 } from "node:fs";
|
|
6355
|
-
import { mkdir as
|
|
7023
|
+
import { mkdir as mkdir15, writeFile as writeFile6 } from "node:fs/promises";
|
|
6356
7024
|
import { spawn } from "node:child_process";
|
|
6357
|
-
import { join as
|
|
7025
|
+
import { join as join22, basename as basename2, dirname as dirname5 } from "node:path";
|
|
6358
7026
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
6359
7027
|
var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD_SESSION_FILE = ".mktg/dashboard-session.json", OUTPUT_DIRS, CONTENT_SKILLS, DISTRIBUTION_SKILLS, schema14, parseDashboardArgs = (args) => {
|
|
6360
7028
|
const first = args[0];
|
|
@@ -6490,8 +7158,8 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6490
7158
|
const passes = doctor.checks.filter((check) => check.status === "pass").length;
|
|
6491
7159
|
const warns = doctor.checks.filter((check) => check.status === "warn").length;
|
|
6492
7160
|
const fails = doctor.checks.filter((check) => check.status === "fail").length;
|
|
6493
|
-
const creativeKitPath =
|
|
6494
|
-
const playgroundPath =
|
|
7161
|
+
const creativeKitPath = join22(flags.cwd, "brand", "creative-kit.md");
|
|
7162
|
+
const playgroundPath = join22(flags.cwd, "brand-playground.html");
|
|
6495
7163
|
return {
|
|
6496
7164
|
version: DASHBOARD_SNAPSHOT_VERSION,
|
|
6497
7165
|
project: {
|
|
@@ -6594,7 +7262,7 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6594
7262
|
}, discoverOutputs = async (cwd) => {
|
|
6595
7263
|
const outputs = [];
|
|
6596
7264
|
for (const dir of OUTPUT_DIRS) {
|
|
6597
|
-
const dirPath =
|
|
7265
|
+
const dirPath = join22(cwd, dir);
|
|
6598
7266
|
if (!existsSync3(dirPath))
|
|
6599
7267
|
continue;
|
|
6600
7268
|
const glob = new Bun.Glob("**/*.{md,mdx,txt,html,json,png,jpg,jpeg,gif,svg}");
|
|
@@ -6610,7 +7278,7 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6610
7278
|
});
|
|
6611
7279
|
}
|
|
6612
7280
|
}
|
|
6613
|
-
const playgroundPath =
|
|
7281
|
+
const playgroundPath = join22(cwd, "brand-playground.html");
|
|
6614
7282
|
if (existsSync3(playgroundPath)) {
|
|
6615
7283
|
outputs.push({
|
|
6616
7284
|
id: "output-brand-playground",
|
|
@@ -6681,7 +7349,7 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6681
7349
|
for await (const relPath of glob.scan({ cwd })) {
|
|
6682
7350
|
if (relPath.startsWith("node_modules/") || relPath.startsWith(".git/"))
|
|
6683
7351
|
continue;
|
|
6684
|
-
const absolutePath =
|
|
7352
|
+
const absolutePath = join22(cwd, relPath);
|
|
6685
7353
|
try {
|
|
6686
7354
|
const parsed = await Bun.file(absolutePath).json();
|
|
6687
7355
|
manifests.push({
|
|
@@ -6763,7 +7431,7 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6763
7431
|
capabilityIndex: CAPABILITY_INDEX
|
|
6764
7432
|
};
|
|
6765
7433
|
}, loadCompeteWatchList = async (cwd) => {
|
|
6766
|
-
const file = Bun.file(
|
|
7434
|
+
const file = Bun.file(join22(cwd, ".mktg", "compete", "watchlist.json"));
|
|
6767
7435
|
if (!await file.exists())
|
|
6768
7436
|
return { version: 1, entries: [] };
|
|
6769
7437
|
try {
|
|
@@ -6887,7 +7555,7 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6887
7555
|
}, handleLaunch = async (flags) => {
|
|
6888
7556
|
const token = randomUUID();
|
|
6889
7557
|
const url = `http://${DASHBOARD_HOST}:${DASHBOARD_PORT}/dashboard`;
|
|
6890
|
-
const sessionPath =
|
|
7558
|
+
const sessionPath = join22(flags.cwd, DASHBOARD_SESSION_FILE);
|
|
6891
7559
|
const payload = {
|
|
6892
7560
|
launched: !flags.dryRun,
|
|
6893
7561
|
projectRoot: flags.cwd,
|
|
@@ -6900,11 +7568,11 @@ var SUBCOMMANDS3, DASHBOARD_PORT = 4311, DASHBOARD_HOST = "127.0.0.1", DASHBOARD
|
|
|
6900
7568
|
};
|
|
6901
7569
|
if (flags.dryRun)
|
|
6902
7570
|
return ok(payload);
|
|
6903
|
-
await
|
|
6904
|
-
await
|
|
7571
|
+
await mkdir15(join22(flags.cwd, ".mktg"), { recursive: true });
|
|
7572
|
+
await writeFile6(sessionPath, JSON.stringify({ token, projectRoot: flags.cwd, url, createdAt: new Date().toISOString() }, null, 2), { mode: 384 });
|
|
6905
7573
|
const moduleDir2 = dirname5(fileURLToPath2(import.meta.url));
|
|
6906
|
-
const rootWebsiteDir =
|
|
6907
|
-
const localWebsiteDir =
|
|
7574
|
+
const rootWebsiteDir = join22(moduleDir2, "..", "..", "website");
|
|
7575
|
+
const localWebsiteDir = join22(flags.cwd, "website");
|
|
6908
7576
|
const spawnCwd = existsSync3(localWebsiteDir) ? localWebsiteDir : rootWebsiteDir;
|
|
6909
7577
|
const proc = spawn("bun", ["x", "next", "dev", "--hostname", DASHBOARD_HOST, "--port", String(DASHBOARD_PORT)], {
|
|
6910
7578
|
cwd: spawnCwd,
|
|
@@ -7054,11 +7722,373 @@ var init_dashboard = __esm(() => {
|
|
|
7054
7722
|
];
|
|
7055
7723
|
});
|
|
7056
7724
|
|
|
7725
|
+
// src/commands/catalog.ts
|
|
7726
|
+
var exports_catalog = {};
|
|
7727
|
+
__export(exports_catalog, {
|
|
7728
|
+
syncSchema: () => syncSchema,
|
|
7729
|
+
statusSchema: () => statusSchema,
|
|
7730
|
+
schema: () => schema15,
|
|
7731
|
+
listSchema: () => listSchema,
|
|
7732
|
+
infoSchema: () => infoSchema,
|
|
7733
|
+
handler: () => handler15,
|
|
7734
|
+
addSchema: () => addSchema
|
|
7735
|
+
});
|
|
7736
|
+
var loadErrorToResult = (result) => {
|
|
7737
|
+
if (result.reason === "manifest-missing") {
|
|
7738
|
+
return err("CATALOG_MANIFEST_INVALID", `catalogs-manifest.json not found at ${result.path}`, ["Run `mktg catalog add <name> --input '{...}' --confirm` to create the manifest", "Or ship catalogs-manifest.json in the package root"], 2);
|
|
7739
|
+
}
|
|
7740
|
+
if (result.reason === "manifest-invalid") {
|
|
7741
|
+
return err("CATALOG_MANIFEST_INVALID", `catalogs-manifest.json is malformed: ${result.detail}`, result.path ? [`Offending path: ${result.path}`, "Compare against docs/integration/catalog-design.md §1 (CatalogEntry type)"] : ["Compare against docs/integration/catalog-design.md §1 (CatalogEntry type)"], 2);
|
|
7742
|
+
}
|
|
7743
|
+
if (result.reason === "collision") {
|
|
7744
|
+
const lines2 = result.collisions.map((c) => `- ${c.kind}:${c.adapter} declared by: ${c.declaredBy.join(", ")}`);
|
|
7745
|
+
return err("CATALOG_COLLISION", `Two or more catalogs declare the same adapter name`, ["Adapter names must be globally unique across all catalogs + built-in adapters:", ...lines2, "Rename one or remove the duplicate"], 2);
|
|
7746
|
+
}
|
|
7747
|
+
const lines = result.denials.map((d) => `- '${d.catalog}' (license: ${d.license}) declares transport: '${d.transport}'${d.sdk_reference ? `, sdk_reference: '${d.sdk_reference}'` : ""}`);
|
|
7748
|
+
return err("CATALOG_LICENSE_DENIED", "Copyleft-licensed catalog cannot declare transport: 'sdk' or a non-null sdk_reference", [...lines, "Copyleft catalogs must use transport: 'http' AND sdk_reference: null (raw-fetch path)"], 2);
|
|
7749
|
+
}, parseNamePositional = (args, subcommand) => {
|
|
7750
|
+
const positional = args.filter((a) => !a.startsWith("--"));
|
|
7751
|
+
const name = positional[1];
|
|
7752
|
+
if (!name) {
|
|
7753
|
+
return {
|
|
7754
|
+
ok: false,
|
|
7755
|
+
result: invalidArgs(`Missing catalog name for 'catalog ${subcommand}'`, [`Usage: mktg catalog ${subcommand} <name> [--json]`, "mktg catalog list --json to see registered catalogs"])
|
|
7756
|
+
};
|
|
7757
|
+
}
|
|
7758
|
+
const idCheck = validateResourceId(name, "catalog");
|
|
7759
|
+
if (!idCheck.ok)
|
|
7760
|
+
return { ok: false, result: invalidArgs(idCheck.message) };
|
|
7761
|
+
const ctrlCheck = rejectControlChars(name, "catalog");
|
|
7762
|
+
if (!ctrlCheck.ok)
|
|
7763
|
+
return { ok: false, result: invalidArgs(ctrlCheck.message) };
|
|
7764
|
+
return { ok: true, name };
|
|
7765
|
+
}, listSchema, infoSchema, syncSchema, statusSchema, addSchema, schema15, handleList = async () => {
|
|
7766
|
+
const result = await loadCatalogManifest();
|
|
7767
|
+
if (!result.ok)
|
|
7768
|
+
return loadErrorToResult(result);
|
|
7769
|
+
const catalogs = Object.values(result.manifest.catalogs);
|
|
7770
|
+
let installed = 0;
|
|
7771
|
+
for (const c of catalogs) {
|
|
7772
|
+
const { configured } = computeConfiguredStatus(c);
|
|
7773
|
+
if (configured)
|
|
7774
|
+
installed++;
|
|
7775
|
+
}
|
|
7776
|
+
return ok({ catalogs, installed, total: catalogs.length });
|
|
7777
|
+
}, handleInfo2 = async (args) => {
|
|
7778
|
+
const parsed = parseNamePositional(args, "info");
|
|
7779
|
+
if (!parsed.ok)
|
|
7780
|
+
return parsed.result;
|
|
7781
|
+
const result = await loadCatalogManifest();
|
|
7782
|
+
if (!result.ok)
|
|
7783
|
+
return loadErrorToResult(result);
|
|
7784
|
+
const entry = getCatalog(result.manifest, parsed.name);
|
|
7785
|
+
if (!entry) {
|
|
7786
|
+
return err("CATALOG_NOT_FOUND", `Catalog '${parsed.name}' not found in catalogs-manifest.json`, [`Registered catalogs: ${Object.keys(result.manifest.catalogs).join(", ") || "(none)"}`, "mktg catalog list --json"], 1);
|
|
7787
|
+
}
|
|
7788
|
+
const status = computeConfiguredStatus(entry);
|
|
7789
|
+
return ok({
|
|
7790
|
+
...entry,
|
|
7791
|
+
configured: status.configured,
|
|
7792
|
+
missing_envs: status.missingEnvs,
|
|
7793
|
+
resolved_base: status.resolvedBase
|
|
7794
|
+
});
|
|
7795
|
+
}, parseSyncFlags = (args) => {
|
|
7796
|
+
for (let i = 0;i < args.length; i++) {
|
|
7797
|
+
const next = args[i + 1];
|
|
7798
|
+
if (args[i] === "--catalog" && next !== undefined)
|
|
7799
|
+
return { catalogFilter: next };
|
|
7800
|
+
const a = args[i];
|
|
7801
|
+
if (a && a.startsWith("--catalog="))
|
|
7802
|
+
return { catalogFilter: a.slice(10) };
|
|
7803
|
+
}
|
|
7804
|
+
return {};
|
|
7805
|
+
}, handleSync = async (args) => {
|
|
7806
|
+
const result = await loadCatalogManifest();
|
|
7807
|
+
if (!result.ok)
|
|
7808
|
+
return loadErrorToResult(result);
|
|
7809
|
+
const { catalogFilter } = parseSyncFlags(args);
|
|
7810
|
+
const entries = Object.values(result.manifest.catalogs).filter((c) => !catalogFilter || c.name === catalogFilter);
|
|
7811
|
+
const items = entries.map((c) => ({
|
|
7812
|
+
name: c.name,
|
|
7813
|
+
from_version: c.version_pinned,
|
|
7814
|
+
to_version: null,
|
|
7815
|
+
changed: false,
|
|
7816
|
+
error: "upstream version check not yet implemented — see plan v2 §Phase A-prime"
|
|
7817
|
+
}));
|
|
7818
|
+
return ok({
|
|
7819
|
+
catalogs: items,
|
|
7820
|
+
summary: {
|
|
7821
|
+
total: items.length,
|
|
7822
|
+
changed: items.filter((i) => i.changed).length,
|
|
7823
|
+
errors: items.filter((i) => i.error !== undefined).length
|
|
7824
|
+
},
|
|
7825
|
+
dryRun: true
|
|
7826
|
+
});
|
|
7827
|
+
}, handleStatus = async () => {
|
|
7828
|
+
const result = await loadCatalogManifest();
|
|
7829
|
+
if (!result.ok)
|
|
7830
|
+
return loadErrorToResult(result);
|
|
7831
|
+
const items = Object.values(result.manifest.catalogs).map((c) => {
|
|
7832
|
+
const s = computeConfiguredStatus(c);
|
|
7833
|
+
const detail = s.configured ? `configured — ${c.transport} transport to ${s.resolvedBase ?? "<no base>"}` : `unconfigured — missing: ${s.missingEnvs.join(", ")}`;
|
|
7834
|
+
return {
|
|
7835
|
+
name: c.name,
|
|
7836
|
+
configured: s.configured,
|
|
7837
|
+
healthy: null,
|
|
7838
|
+
detail
|
|
7839
|
+
};
|
|
7840
|
+
});
|
|
7841
|
+
return ok({
|
|
7842
|
+
catalogs: items,
|
|
7843
|
+
summary: {
|
|
7844
|
+
total: items.length,
|
|
7845
|
+
configured: items.filter((i) => i.configured).length,
|
|
7846
|
+
healthy: items.filter((i) => i.healthy === true).length
|
|
7847
|
+
}
|
|
7848
|
+
});
|
|
7849
|
+
}, validateEntryShape = async (entry, name) => {
|
|
7850
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
7851
|
+
return { ok: false, detail: "entry must be an object", path: "" };
|
|
7852
|
+
}
|
|
7853
|
+
const withName = { ...entry, name };
|
|
7854
|
+
const syntheticManifest = { version: 1, catalogs: { [name]: withName } };
|
|
7855
|
+
const { _testing: _testing2 } = await Promise.resolve().then(() => (init_catalogs(), exports_catalogs));
|
|
7856
|
+
const check = _testing2.validateShape(syntheticManifest);
|
|
7857
|
+
if (!check.ok) {
|
|
7858
|
+
return { ok: false, detail: check.detail, path: check.path };
|
|
7859
|
+
}
|
|
7860
|
+
return { ok: true, entry: check.manifest.catalogs[name] };
|
|
7861
|
+
}, handleAdd2 = async (args, flags) => {
|
|
7862
|
+
const parsed = parseNamePositional(args, "add");
|
|
7863
|
+
if (!parsed.ok)
|
|
7864
|
+
return parsed.result;
|
|
7865
|
+
const raw = flags.jsonInput;
|
|
7866
|
+
if (!raw) {
|
|
7867
|
+
return invalidArgs("Missing --input flag with CatalogEntry JSON payload", [
|
|
7868
|
+
"Usage: mktg catalog add <name> --input '<json>' --confirm --json",
|
|
7869
|
+
`Example: mktg catalog add postiz --input '{"name":"postiz",...}' --dry-run --json`
|
|
7870
|
+
]);
|
|
7871
|
+
}
|
|
7872
|
+
const payload = parseJsonInput(raw);
|
|
7873
|
+
if (!payload.ok)
|
|
7874
|
+
return invalidArgs(`Invalid catalog JSON: ${payload.message}`);
|
|
7875
|
+
const shapeCheck = await validateEntryShape(payload.data, parsed.name);
|
|
7876
|
+
if (!shapeCheck.ok) {
|
|
7877
|
+
return err("CATALOG_MANIFEST_INVALID", `Payload fails CatalogEntry shape check: ${shapeCheck.detail}`, shapeCheck.path ? [`Offending path: ${shapeCheck.path}`, "See docs/integration/catalog-design.md §1"] : ["See docs/integration/catalog-design.md §1"], 2);
|
|
7878
|
+
}
|
|
7879
|
+
const confirm = args.includes("--confirm");
|
|
7880
|
+
const explicitDryRun = args.includes("--dry-run") || flags.dryRun;
|
|
7881
|
+
const effectiveDryRun = explicitDryRun || !confirm;
|
|
7882
|
+
const { loadCatalogManifest: loader } = await Promise.resolve().then(() => (init_catalogs(), exports_catalogs));
|
|
7883
|
+
const current = await loader();
|
|
7884
|
+
const existingCatalogs = current.ok ? current.manifest.catalogs : {};
|
|
7885
|
+
if (!current.ok && current.reason !== "manifest-missing") {
|
|
7886
|
+
return loadErrorToResult(current);
|
|
7887
|
+
}
|
|
7888
|
+
const before = existingCatalogs[parsed.name] ?? null;
|
|
7889
|
+
const mergedCatalogs = { ...existingCatalogs, [parsed.name]: shapeCheck.entry };
|
|
7890
|
+
const mergedManifest = { version: 1, catalogs: mergedCatalogs };
|
|
7891
|
+
const { detectAdapterCollisions: detectAdapterCollisions2, detectLicenseDenials: detectLicenseDenials2 } = await Promise.resolve().then(() => (init_catalogs(), exports_catalogs));
|
|
7892
|
+
const collisions = detectAdapterCollisions2(mergedManifest, { publish_adapters: DEFAULT_BUILTIN_PUBLISH_ADAPTERS });
|
|
7893
|
+
if (collisions.length > 0) {
|
|
7894
|
+
return loadErrorToResult({ ok: false, reason: "collision", collisions });
|
|
7895
|
+
}
|
|
7896
|
+
const denials = detectLicenseDenials2(mergedManifest);
|
|
7897
|
+
if (denials.length > 0) {
|
|
7898
|
+
return loadErrorToResult({ ok: false, reason: "license-denied", denials });
|
|
7899
|
+
}
|
|
7900
|
+
const { getPackageRoot: getPackageRoot2 } = await Promise.resolve().then(() => (init_paths(), exports_paths));
|
|
7901
|
+
const { join: join23 } = await import("node:path");
|
|
7902
|
+
const manifestPath = join23(getPackageRoot2(), "catalogs-manifest.json");
|
|
7903
|
+
if (!effectiveDryRun) {
|
|
7904
|
+
await Bun.write(manifestPath, JSON.stringify(mergedManifest, null, 2) + `
|
|
7905
|
+
`);
|
|
7906
|
+
}
|
|
7907
|
+
return ok({
|
|
7908
|
+
name: parsed.name,
|
|
7909
|
+
added: !effectiveDryRun,
|
|
7910
|
+
location: manifestPath,
|
|
7911
|
+
before,
|
|
7912
|
+
after: shapeCheck.entry,
|
|
7913
|
+
dryRun: effectiveDryRun
|
|
7914
|
+
});
|
|
7915
|
+
}, handler15 = async (args, flags) => {
|
|
7916
|
+
const positional = args.filter((a) => !a.startsWith("--"));
|
|
7917
|
+
const sub = positional[0];
|
|
7918
|
+
if (!sub) {
|
|
7919
|
+
return invalidArgs("Missing subcommand", [
|
|
7920
|
+
"Usage: mktg catalog <list | info | sync | status | add> [...args]",
|
|
7921
|
+
"mktg catalog list --json"
|
|
7922
|
+
]);
|
|
7923
|
+
}
|
|
7924
|
+
switch (sub) {
|
|
7925
|
+
case "list":
|
|
7926
|
+
return handleList();
|
|
7927
|
+
case "info":
|
|
7928
|
+
return handleInfo2(args);
|
|
7929
|
+
case "sync":
|
|
7930
|
+
return handleSync(args);
|
|
7931
|
+
case "status":
|
|
7932
|
+
return handleStatus();
|
|
7933
|
+
case "add":
|
|
7934
|
+
return handleAdd2(args, flags);
|
|
7935
|
+
default:
|
|
7936
|
+
return invalidArgs(`Unknown catalog subcommand: '${sub}'`, ["Available: list, info, sync, status, add"]);
|
|
7937
|
+
}
|
|
7938
|
+
};
|
|
7939
|
+
var init_catalog = __esm(() => {
|
|
7940
|
+
init_types();
|
|
7941
|
+
init_errors();
|
|
7942
|
+
init_catalogs();
|
|
7943
|
+
listSchema = {
|
|
7944
|
+
name: "list",
|
|
7945
|
+
description: "List registered catalogs from catalogs-manifest.json",
|
|
7946
|
+
flags: [],
|
|
7947
|
+
output: {
|
|
7948
|
+
catalogs: "CatalogEntry[] — registered catalogs in manifest order",
|
|
7949
|
+
"catalogs.*.name": "string — catalog identifier",
|
|
7950
|
+
"catalogs.*.repo_url": "string — upstream GitHub repo URL",
|
|
7951
|
+
"catalogs.*.docs_url": "string — upstream documentation URL",
|
|
7952
|
+
"catalogs.*.license": "string — SPDX license identifier",
|
|
7953
|
+
"catalogs.*.version_pinned": "string — upstream release tag",
|
|
7954
|
+
"catalogs.*.transport": "'sdk' | 'http' — how this catalog talks to upstream",
|
|
7955
|
+
"catalogs.*.sdk_reference": "string | null — npm package when transport='sdk'; null when transport='http'",
|
|
7956
|
+
"catalogs.*.auth.style": "'bearer' | 'basic' | 'oauth2' | 'none'",
|
|
7957
|
+
"catalogs.*.auth.base_env": "string — env var name for API base URL",
|
|
7958
|
+
"catalogs.*.auth.credential_envs": "string[] — env var names for credentials",
|
|
7959
|
+
"catalogs.*.auth.header_format": "'bearer' | 'bare' — HTTP Authorization header format (optional)",
|
|
7960
|
+
"catalogs.*.capabilities.publish_adapters": "string[] — publish adapter names this catalog contributes",
|
|
7961
|
+
"catalogs.*.capabilities.scheduling_adapters": "string[] — scheduling adapter names",
|
|
7962
|
+
"catalogs.*.capabilities.email_adapters": "string[] — email adapter names",
|
|
7963
|
+
"catalogs.*.skills": "string[] — SKILL.md names this catalog contributes",
|
|
7964
|
+
installed: "number — catalogs whose auth envs are all set in process.env",
|
|
7965
|
+
total: "number — total catalogs registered"
|
|
7966
|
+
},
|
|
7967
|
+
examples: [
|
|
7968
|
+
{ args: "mktg catalog list --json", description: "Full catalog list" },
|
|
7969
|
+
{ args: "mktg catalog list --fields catalogs.name --json", description: "Just catalog names" }
|
|
7970
|
+
],
|
|
7971
|
+
vocabulary: ["catalog list", "list catalogs", "upstream"]
|
|
7972
|
+
};
|
|
7973
|
+
infoSchema = {
|
|
7974
|
+
name: "info",
|
|
7975
|
+
description: "Show full metadata for a single catalog, with computed env-configured state",
|
|
7976
|
+
flags: [],
|
|
7977
|
+
positional: { name: "name", description: "Catalog identifier", required: true },
|
|
7978
|
+
output: {
|
|
7979
|
+
name: "string — catalog identifier",
|
|
7980
|
+
repo_url: "string",
|
|
7981
|
+
docs_url: "string",
|
|
7982
|
+
license: "string — SPDX identifier",
|
|
7983
|
+
version_pinned: "string — upstream release tag",
|
|
7984
|
+
transport: "'sdk' | 'http'",
|
|
7985
|
+
sdk_reference: "string | null",
|
|
7986
|
+
"auth.style": "'bearer' | 'basic' | 'oauth2' | 'none'",
|
|
7987
|
+
"auth.base_env": "string",
|
|
7988
|
+
"auth.credential_envs": "string[]",
|
|
7989
|
+
"auth.header_format": "'bearer' | 'bare'",
|
|
7990
|
+
"capabilities.publish_adapters": "string[]",
|
|
7991
|
+
"capabilities.scheduling_adapters": "string[]",
|
|
7992
|
+
"capabilities.email_adapters": "string[]",
|
|
7993
|
+
skills: "string[]",
|
|
7994
|
+
configured: "boolean — true iff every auth env var is set in process.env",
|
|
7995
|
+
missing_envs: "string[] — auth env var names that are currently unset",
|
|
7996
|
+
resolved_base: "string | null — process.env[auth.base_env] if set, else null"
|
|
7997
|
+
},
|
|
7998
|
+
examples: [
|
|
7999
|
+
{ args: "mktg catalog info postiz --json", description: "Full postiz metadata + configured status" },
|
|
8000
|
+
{ args: "mktg catalog info postiz --fields configured,missing_envs --json", description: "Just runtime readiness" }
|
|
8001
|
+
],
|
|
8002
|
+
vocabulary: ["catalog info", "catalog metadata"]
|
|
8003
|
+
};
|
|
8004
|
+
syncSchema = {
|
|
8005
|
+
name: "sync",
|
|
8006
|
+
description: "Check each catalog's pinned version against upstream (v1: read-only, reports drift without mutating)",
|
|
8007
|
+
flags: [
|
|
8008
|
+
{ name: "--dry-run", type: "boolean", required: false, default: false, description: "Preview version diffs without writing (v1 is always read-only — flag accepted for forward-compat)" },
|
|
8009
|
+
{ name: "--catalog", type: "string", required: false, description: "Limit sync to a single catalog" }
|
|
8010
|
+
],
|
|
8011
|
+
output: {
|
|
8012
|
+
catalogs: "Array<{name, from_version, to_version, changed}> — per-catalog diff",
|
|
8013
|
+
"catalogs.*.name": "string",
|
|
8014
|
+
"catalogs.*.from_version": "string — currently pinned version",
|
|
8015
|
+
"catalogs.*.to_version": "string | null — latest upstream tag, or null on network error",
|
|
8016
|
+
"catalogs.*.changed": "boolean — true if from_version !== to_version",
|
|
8017
|
+
"catalogs.*.error": "string | undefined — present when upstream check failed",
|
|
8018
|
+
"summary.total": "number",
|
|
8019
|
+
"summary.changed": "number — catalogs with pending bumps",
|
|
8020
|
+
"summary.errors": "number — upstream check failures",
|
|
8021
|
+
dryRun: "boolean"
|
|
8022
|
+
},
|
|
8023
|
+
examples: [
|
|
8024
|
+
{ args: "mktg catalog sync --json", description: "Check all catalogs for upstream version drift" },
|
|
8025
|
+
{ args: "mktg catalog sync --catalog postiz --json", description: "Check one catalog only" }
|
|
8026
|
+
],
|
|
8027
|
+
vocabulary: ["catalog sync", "upstream version"]
|
|
8028
|
+
};
|
|
8029
|
+
statusSchema = {
|
|
8030
|
+
name: "status",
|
|
8031
|
+
description: "Health snapshot across all catalogs: configured + reachable",
|
|
8032
|
+
flags: [],
|
|
8033
|
+
output: {
|
|
8034
|
+
catalogs: "Array<{name, configured, healthy, detail}>",
|
|
8035
|
+
"catalogs.*.name": "string",
|
|
8036
|
+
"catalogs.*.configured": "boolean — all auth env vars set",
|
|
8037
|
+
"catalogs.*.healthy": "boolean | null — reachable + auth ok (null when unconfigured or read-only path)",
|
|
8038
|
+
"catalogs.*.detail": "string — human-readable status message",
|
|
8039
|
+
"summary.total": "number",
|
|
8040
|
+
"summary.configured": "number",
|
|
8041
|
+
"summary.healthy": "number"
|
|
8042
|
+
},
|
|
8043
|
+
examples: [
|
|
8044
|
+
{ args: "mktg catalog status --json", description: "All catalog health at a glance" },
|
|
8045
|
+
{ args: "mktg catalog status --fields summary --json", description: "Summary counts only" }
|
|
8046
|
+
],
|
|
8047
|
+
vocabulary: ["catalog status", "catalog health"]
|
|
8048
|
+
};
|
|
8049
|
+
addSchema = {
|
|
8050
|
+
name: "add",
|
|
8051
|
+
description: "Register a new catalog in catalogs-manifest.json (mutating)",
|
|
8052
|
+
flags: [
|
|
8053
|
+
{ name: "--dry-run", type: "boolean", required: false, default: false, description: "Preview the manifest change without writing" },
|
|
8054
|
+
{ name: "--confirm", type: "boolean", required: false, default: false, description: "Required to actually write the manifest" }
|
|
8055
|
+
],
|
|
8056
|
+
positional: { name: "name", description: "Catalog identifier (must match resource-id regex)", required: true },
|
|
8057
|
+
output: {
|
|
8058
|
+
name: "string — catalog identifier that was added",
|
|
8059
|
+
added: "boolean — true when manifest was written; false on --dry-run or confirm-missing",
|
|
8060
|
+
location: "string — absolute path to catalogs-manifest.json",
|
|
8061
|
+
before: "CatalogEntry | null — previous entry when this was an overwrite",
|
|
8062
|
+
after: "CatalogEntry — final entry",
|
|
8063
|
+
dryRun: "boolean"
|
|
8064
|
+
},
|
|
8065
|
+
examples: [
|
|
8066
|
+
{ args: `mktg catalog add postiz --input '{...}' --dry-run --json`, description: "Preview adding postiz via inline JSON" },
|
|
8067
|
+
{ args: `mktg catalog add postiz --input '{...}' --confirm --json`, description: "Actually write postiz to manifest" }
|
|
8068
|
+
],
|
|
8069
|
+
vocabulary: ["catalog add", "register catalog", "new catalog"]
|
|
8070
|
+
};
|
|
8071
|
+
schema15 = {
|
|
8072
|
+
name: "catalog",
|
|
8073
|
+
description: "Manage upstream-catalog registry (list, info, sync, status, add). Catalogs are external OSS projects mktg integrates with via SDK or raw HTTP.",
|
|
8074
|
+
flags: [],
|
|
8075
|
+
positional: { name: "subcommand", description: "Subcommand to run", required: true },
|
|
8076
|
+
subcommands: [listSchema, infoSchema, syncSchema, statusSchema, addSchema],
|
|
8077
|
+
output: {},
|
|
8078
|
+
examples: [
|
|
8079
|
+
{ args: "mktg catalog list --json", description: "List registered catalogs" },
|
|
8080
|
+
{ args: "mktg catalog info postiz --json", description: "Show postiz metadata" },
|
|
8081
|
+
{ args: "mktg catalog status --json", description: "Health check across all catalogs" }
|
|
8082
|
+
],
|
|
8083
|
+
vocabulary: ["catalog", "catalogs", "upstream", "integration"]
|
|
8084
|
+
};
|
|
8085
|
+
});
|
|
8086
|
+
|
|
7057
8087
|
// src/commands/schema.ts
|
|
7058
8088
|
var exports_schema = {};
|
|
7059
8089
|
__export(exports_schema, {
|
|
7060
|
-
schema: () =>
|
|
7061
|
-
handler: () =>
|
|
8090
|
+
schema: () => schema16,
|
|
8091
|
+
handler: () => handler16
|
|
7062
8092
|
});
|
|
7063
8093
|
var VERSION, GLOBAL_FLAGS, EXIT_CODES, loadSchemas = async () => {
|
|
7064
8094
|
const modules = await Promise.all([
|
|
@@ -7075,15 +8105,16 @@ var VERSION, GLOBAL_FLAGS, EXIT_CODES, loadSchemas = async () => {
|
|
|
7075
8105
|
Promise.resolve().then(() => (init_plan(), exports_plan)).then((m) => m.schema).catch(() => null),
|
|
7076
8106
|
Promise.resolve().then(() => (init_publish(), exports_publish)).then((m) => m.schema).catch(() => null),
|
|
7077
8107
|
Promise.resolve().then(() => (init_compete(), exports_compete)).then((m) => m.schema).catch(() => null),
|
|
7078
|
-
Promise.resolve().then(() => (init_dashboard(), exports_dashboard)).then((m) => m.schema).catch(() => null)
|
|
8108
|
+
Promise.resolve().then(() => (init_dashboard(), exports_dashboard)).then((m) => m.schema).catch(() => null),
|
|
8109
|
+
Promise.resolve().then(() => (init_catalog(), exports_catalog)).then((m) => m.schema).catch(() => null)
|
|
7079
8110
|
]);
|
|
7080
|
-
const schemas = { schema:
|
|
8111
|
+
const schemas = { schema: schema16 };
|
|
7081
8112
|
for (const s of modules) {
|
|
7082
8113
|
if (s)
|
|
7083
8114
|
schemas[s.name] = s;
|
|
7084
8115
|
}
|
|
7085
8116
|
return schemas;
|
|
7086
|
-
},
|
|
8117
|
+
}, schema16, extractEnumValues = (typeStr) => {
|
|
7087
8118
|
const matches = typeStr.match(/'([^']+)'/g);
|
|
7088
8119
|
if (matches && matches.length >= 2) {
|
|
7089
8120
|
return matches.map((s) => s.replace(/'/g, ""));
|
|
@@ -7132,7 +8163,7 @@ var VERSION, GLOBAL_FLAGS, EXIT_CODES, loadSchemas = async () => {
|
|
|
7132
8163
|
responseSchema: parseOutputToResponseSchema(sub.output)
|
|
7133
8164
|
}))
|
|
7134
8165
|
}
|
|
7135
|
-
}),
|
|
8166
|
+
}), handler16 = async (args, _flags) => {
|
|
7136
8167
|
const schemas = await loadSchemas();
|
|
7137
8168
|
const positionalArgs = args.filter((a) => !a.startsWith("--"));
|
|
7138
8169
|
if (positionalArgs.length === 0) {
|
|
@@ -7176,7 +8207,7 @@ var init_schema = __esm(() => {
|
|
|
7176
8207
|
5: "Network error (web research, API call)",
|
|
7177
8208
|
6: "Not implemented (temporary, for stub commands)"
|
|
7178
8209
|
};
|
|
7179
|
-
|
|
8210
|
+
schema16 = {
|
|
7180
8211
|
name: "schema",
|
|
7181
8212
|
description: "Introspect CLI commands, flags, and output shapes — the single source of truth for any agent using this CLI",
|
|
7182
8213
|
flags: [],
|
|
@@ -7394,6 +8425,7 @@ Commands:
|
|
|
7394
8425
|
plan Execution loop — prioritized task queue from project state
|
|
7395
8426
|
status Project marketing state snapshot
|
|
7396
8427
|
dashboard Local command center — snapshot, actions, launch
|
|
8428
|
+
catalog Upstream catalog registry (list, info, sync, status, add)
|
|
7397
8429
|
list Show available skills
|
|
7398
8430
|
update Force-update skills
|
|
7399
8431
|
schema Introspect CLI commands and output shapes
|
|
@@ -7472,27 +8504,28 @@ var COMMANDS = {
|
|
|
7472
8504
|
plan: () => Promise.resolve().then(() => (init_plan(), exports_plan)),
|
|
7473
8505
|
publish: () => Promise.resolve().then(() => (init_publish(), exports_publish)),
|
|
7474
8506
|
compete: () => Promise.resolve().then(() => (init_compete(), exports_compete)),
|
|
7475
|
-
dashboard: () => Promise.resolve().then(() => (init_dashboard(), exports_dashboard))
|
|
8507
|
+
dashboard: () => Promise.resolve().then(() => (init_dashboard(), exports_dashboard)),
|
|
8508
|
+
catalog: () => Promise.resolve().then(() => (init_catalog(), exports_catalog))
|
|
7476
8509
|
};
|
|
7477
|
-
var formatSchemaAsHelp = (
|
|
7478
|
-
const lines = [`mktg ${
|
|
7479
|
-
if (
|
|
8510
|
+
var formatSchemaAsHelp = (schema17) => {
|
|
8511
|
+
const lines = [`mktg ${schema17.name} — ${schema17.description}`];
|
|
8512
|
+
if (schema17.subcommands && schema17.subcommands.length > 0) {
|
|
7480
8513
|
lines.push("", "Subcommands:");
|
|
7481
|
-
for (const sub of
|
|
8514
|
+
for (const sub of schema17.subcommands) {
|
|
7482
8515
|
lines.push(` ${sub.name.padEnd(14)} ${sub.description}`);
|
|
7483
8516
|
}
|
|
7484
8517
|
}
|
|
7485
|
-
if (
|
|
8518
|
+
if (schema17.flags.length > 0) {
|
|
7486
8519
|
lines.push("", "Flags:");
|
|
7487
|
-
for (const flag of
|
|
8520
|
+
for (const flag of schema17.flags) {
|
|
7488
8521
|
const req = flag.required ? " (required)" : "";
|
|
7489
8522
|
const name = flag.name.startsWith("--") ? flag.name : `--${flag.name}`;
|
|
7490
8523
|
lines.push(` ${name.padEnd(16)} ${flag.description}${req}`);
|
|
7491
8524
|
}
|
|
7492
8525
|
}
|
|
7493
|
-
if (
|
|
8526
|
+
if (schema17.examples.length > 0) {
|
|
7494
8527
|
lines.push("", "Examples:");
|
|
7495
|
-
for (const ex of
|
|
8528
|
+
for (const ex of schema17.examples) {
|
|
7496
8529
|
lines.push(` ${ex.args}`);
|
|
7497
8530
|
lines.push(` ${ex.description}`);
|
|
7498
8531
|
}
|
|
@@ -7557,12 +8590,12 @@ var run = async () => {
|
|
|
7557
8590
|
try {
|
|
7558
8591
|
const mod = await loader();
|
|
7559
8592
|
if (wantsHelp) {
|
|
7560
|
-
const
|
|
7561
|
-
if (
|
|
8593
|
+
const schema17 = mod.schema;
|
|
8594
|
+
if (schema17) {
|
|
7562
8595
|
if (flags.json) {
|
|
7563
|
-
writeStdout(JSON.stringify(
|
|
8596
|
+
writeStdout(JSON.stringify(schema17, null, 2));
|
|
7564
8597
|
} else {
|
|
7565
|
-
writeStdout(formatSchemaAsHelp(
|
|
8598
|
+
writeStdout(formatSchemaAsHelp(schema17));
|
|
7566
8599
|
}
|
|
7567
8600
|
process.exit(0);
|
|
7568
8601
|
}
|