@easysociable/cli 0.1.11 → 0.1.13
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 +4 -2
- package/dist/easysociable.js +517 -38
- package/dist/easysociable.js.map +1 -1
- package/package.json +13 -14
- package/scripts/engine/contracts.mjs +4 -3
- package/skills/ONBOARDING.md +5 -9
- package/skills/easysociable-slideshow/SKILL.md +68 -22
- package/skills/easysociable-slideshow/references/CONTENT_PACK.md +13 -10
- package/skills/easysociable-slideshow/workflows/CREATE_RUN.md +20 -16
- package/skills/easysociable-template/scripts/generate-theme-from-hex.mjs +0 -0
package/dist/easysociable.js
CHANGED
|
@@ -15545,7 +15545,8 @@ var init_src = __esm({
|
|
|
15545
15545
|
previewImageUrl: external_exports.string().url().optional(),
|
|
15546
15546
|
/** Layout Family id — catalog UIs group variants by this (ADR-0033). */
|
|
15547
15547
|
familyId: external_exports.string().optional(),
|
|
15548
|
-
familyRole: external_exports.enum(["master", "variant"]).optional()
|
|
15548
|
+
familyRole: external_exports.enum(["master", "variant"]).optional(),
|
|
15549
|
+
familyName: external_exports.string().optional()
|
|
15549
15550
|
}).strict();
|
|
15550
15551
|
publicTemplateSlotTypeSchema = external_exports.enum(["text", "number", "image"]);
|
|
15551
15552
|
publicTemplateSlotSchema = external_exports.object({
|
|
@@ -16061,6 +16062,7 @@ var init_src = __esm({
|
|
|
16061
16062
|
previewThemeId: external_exports.string().nullable().optional(),
|
|
16062
16063
|
familyId: external_exports.string().optional(),
|
|
16063
16064
|
familyRole: external_exports.enum(["master", "variant"]).optional(),
|
|
16065
|
+
familyName: external_exports.string().optional(),
|
|
16064
16066
|
variants: external_exports.array(authoringDraftVariantSchema).optional(),
|
|
16065
16067
|
intendedPlatforms: external_exports.array(external_exports.string()).optional(),
|
|
16066
16068
|
name: external_exports.string(),
|
|
@@ -16115,10 +16117,14 @@ var init_src = __esm({
|
|
|
16115
16117
|
]);
|
|
16116
16118
|
publicSlotValueSchema = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
|
|
16117
16119
|
contentPackPageSchema = external_exports.object({
|
|
16118
|
-
pageTemplateId: external_exports.string().min(1),
|
|
16120
|
+
pageTemplateId: external_exports.string().min(1).optional(),
|
|
16121
|
+
layoutFamilyId: external_exports.string().min(1).optional(),
|
|
16119
16122
|
role: pageRoleSchema,
|
|
16120
16123
|
slots: external_exports.record(external_exports.string(), publicSlotValueSchema)
|
|
16121
|
-
}).strict()
|
|
16124
|
+
}).strict().refine((page) => page.pageTemplateId || page.layoutFamilyId, {
|
|
16125
|
+
message: "page requires layoutFamilyId or pageTemplateId",
|
|
16126
|
+
path: ["layoutFamilyId"]
|
|
16127
|
+
});
|
|
16122
16128
|
contentPackSchema = external_exports.object({
|
|
16123
16129
|
schemaVersion: external_exports.literal("content-pack.v1"),
|
|
16124
16130
|
platform: slideshowPlatformSchema,
|
|
@@ -16143,6 +16149,7 @@ var init_src = __esm({
|
|
|
16143
16149
|
index: external_exports.number().int().nonnegative(),
|
|
16144
16150
|
role: pageRoleSchema,
|
|
16145
16151
|
pageTemplateId: external_exports.string().min(1).optional(),
|
|
16152
|
+
layoutFamilyId: external_exports.string().min(1).optional(),
|
|
16146
16153
|
slots: external_exports.record(external_exports.string(), publicSlotValueSchema),
|
|
16147
16154
|
layout: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
16148
16155
|
style: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
@@ -16297,11 +16304,13 @@ function isRetryableStatus(status) {
|
|
|
16297
16304
|
function sleep(ms) {
|
|
16298
16305
|
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
16299
16306
|
}
|
|
16300
|
-
var TemplateGatewayError, TemplateGatewayClient;
|
|
16307
|
+
var DEFAULT_TEMPLATE_PAGE_SIZE, MAX_TEMPLATE_PAGE_SIZE, TemplateGatewayError, TemplateGatewayClient;
|
|
16301
16308
|
var init_client = __esm({
|
|
16302
16309
|
"../../packages/template-gateway-client/src/client.ts"() {
|
|
16303
16310
|
"use strict";
|
|
16304
16311
|
init_src();
|
|
16312
|
+
DEFAULT_TEMPLATE_PAGE_SIZE = 20;
|
|
16313
|
+
MAX_TEMPLATE_PAGE_SIZE = 50;
|
|
16305
16314
|
TemplateGatewayError = class extends Error {
|
|
16306
16315
|
code;
|
|
16307
16316
|
retryable;
|
|
@@ -16347,6 +16356,13 @@ var init_client = __esm({
|
|
|
16347
16356
|
return response.json;
|
|
16348
16357
|
}
|
|
16349
16358
|
async listTemplates(query) {
|
|
16359
|
+
return (await this.listTemplatesPage(query)).templates;
|
|
16360
|
+
}
|
|
16361
|
+
/**
|
|
16362
|
+
* Same as listTemplates plus `nextCursor` so Agents know the catalog is paged.
|
|
16363
|
+
* Derived client-side (full page ⇒ maybe more) to keep the strict v1 envelope.
|
|
16364
|
+
*/
|
|
16365
|
+
async listTemplatesPage(query) {
|
|
16350
16366
|
const parsed = parseListTemplatesQuery(query ?? {});
|
|
16351
16367
|
const params = new URLSearchParams();
|
|
16352
16368
|
const appendList = (key, values) => {
|
|
@@ -16375,7 +16391,23 @@ var init_client = __esm({
|
|
|
16375
16391
|
const qs = params.toString();
|
|
16376
16392
|
const response = await this.request("GET", qs ? `/v1/templates?${qs}` : "/v1/templates");
|
|
16377
16393
|
this.throwIfError(response.status, response.json);
|
|
16378
|
-
|
|
16394
|
+
const templates = parseTemplateSummaryList(response.json);
|
|
16395
|
+
const limit = parsed.limit ?? DEFAULT_TEMPLATE_PAGE_SIZE;
|
|
16396
|
+
const offset = Number.parseInt(parsed.cursor ?? "0", 10) || 0;
|
|
16397
|
+
return { templates, nextCursor: templates.length >= limit ? String(offset + limit) : null };
|
|
16398
|
+
}
|
|
16399
|
+
/** Drain every page for a filtered query (platform catalogs are tens of templates). */
|
|
16400
|
+
async listAllTemplates(query) {
|
|
16401
|
+
const all = [];
|
|
16402
|
+
let cursor;
|
|
16403
|
+
for (let page = 0; page < 40; page += 1) {
|
|
16404
|
+
const result = await this.listTemplatesPage({ ...query, limit: MAX_TEMPLATE_PAGE_SIZE, cursor });
|
|
16405
|
+
all.push(...result.templates);
|
|
16406
|
+
if (!result.nextCursor)
|
|
16407
|
+
return all;
|
|
16408
|
+
cursor = result.nextCursor;
|
|
16409
|
+
}
|
|
16410
|
+
return all;
|
|
16379
16411
|
}
|
|
16380
16412
|
async getTemplate(templateId) {
|
|
16381
16413
|
const response = await this.request("GET", `/v1/templates/${encodeURIComponent(templateId)}`);
|
|
@@ -16543,6 +16575,12 @@ var init_client = __esm({
|
|
|
16543
16575
|
this.throwIfError(response.status, response.json);
|
|
16544
16576
|
return parseSlideshowRun(response.json);
|
|
16545
16577
|
}
|
|
16578
|
+
/** Per-page shortlist (hard filter → fit score → recent-family penalty → explore slot). Agent still picks. */
|
|
16579
|
+
async recommendSlideshowTemplates(input) {
|
|
16580
|
+
const response = await this.request("POST", "/v1/slideshow/template-recommendations", input);
|
|
16581
|
+
this.throwIfError(response.status, response.json);
|
|
16582
|
+
return response.json.data;
|
|
16583
|
+
}
|
|
16546
16584
|
async listSlideshowRuns(query = {}) {
|
|
16547
16585
|
const input = listSlideshowRunsQuerySchema.parse(query);
|
|
16548
16586
|
const params = new URLSearchParams();
|
|
@@ -16607,7 +16645,7 @@ var init_client = __esm({
|
|
|
16607
16645
|
code: "TEMPLATE_API_KEY_REQUIRED",
|
|
16608
16646
|
message: "Template API key is required",
|
|
16609
16647
|
retryable: false,
|
|
16610
|
-
suggestedAction: "Run `easysociable auth login
|
|
16648
|
+
suggestedAction: "Run `easysociable auth login` and approve in the browser"
|
|
16611
16649
|
});
|
|
16612
16650
|
}
|
|
16613
16651
|
const url2 = `${this.options.baseUrl.replace(/\/$/, "")}/v1/slideshow/runs/${encodeURIComponent(runId)}/artifacts/${pageIndex}`;
|
|
@@ -16802,7 +16840,7 @@ var init_client = __esm({
|
|
|
16802
16840
|
code: "TEMPLATE_API_KEY_REQUIRED",
|
|
16803
16841
|
message: "Template API key is required",
|
|
16804
16842
|
retryable: false,
|
|
16805
|
-
suggestedAction: "Run `easysociable auth login
|
|
16843
|
+
suggestedAction: "Run `easysociable auth login` (browser approval) or set EASYSOCIABLE_ALLOW_ENV_CREDENTIALS=true for local tests"
|
|
16806
16844
|
});
|
|
16807
16845
|
}
|
|
16808
16846
|
const maxRetries = this.options.maxRetries ?? 2;
|
|
@@ -31720,7 +31758,7 @@ var init_src4 = __esm({
|
|
|
31720
31758
|
{
|
|
31721
31759
|
name: "list_templates",
|
|
31722
31760
|
title: "List templates",
|
|
31723
|
-
description: "List public template catalog summaries from Gateway (filterable; no AST)",
|
|
31761
|
+
description: "List public template catalog summaries from Gateway (filterable; no AST). Paged: follow nextCursor, or pass all=true with platforms to get the whole platform catalog before choosing. For Slideshow page choice prefer recommend_slideshow_templates.",
|
|
31724
31762
|
effect: "read-only",
|
|
31725
31763
|
inputSchema: {
|
|
31726
31764
|
type: "object",
|
|
@@ -31734,11 +31772,13 @@ var init_src4 = __esm({
|
|
|
31734
31772
|
requiresImage: { type: "boolean" },
|
|
31735
31773
|
locale: { type: "string" },
|
|
31736
31774
|
q: { type: "string" },
|
|
31737
|
-
limit: { type: "number" }
|
|
31775
|
+
limit: { type: "number" },
|
|
31776
|
+
cursor: { type: "string" },
|
|
31777
|
+
all: { type: "boolean" }
|
|
31738
31778
|
},
|
|
31739
31779
|
additionalProperties: false
|
|
31740
31780
|
},
|
|
31741
|
-
outputSchema: { type: "object", properties: { templates: { type: "array" } } }
|
|
31781
|
+
outputSchema: { type: "object", properties: { templates: { type: "array" }, nextCursor: { type: ["string", "null"] } } }
|
|
31742
31782
|
},
|
|
31743
31783
|
{
|
|
31744
31784
|
name: "get_template",
|
|
@@ -32057,7 +32097,7 @@ var init_src4 = __esm({
|
|
|
32057
32097
|
};
|
|
32058
32098
|
try {
|
|
32059
32099
|
if (toolName === "list_templates") {
|
|
32060
|
-
const
|
|
32100
|
+
const query = {
|
|
32061
32101
|
platforms: args.platforms,
|
|
32062
32102
|
roles: args.roles,
|
|
32063
32103
|
contentFamily: args.contentFamily,
|
|
@@ -32066,10 +32106,16 @@ var init_src4 = __esm({
|
|
|
32066
32106
|
canvasPreset: args.canvasPreset,
|
|
32067
32107
|
requiresImage: typeof args.requiresImage === "boolean" ? args.requiresImage : void 0,
|
|
32068
32108
|
locale: args.locale,
|
|
32069
|
-
q: typeof args.q === "string" ? args.q : void 0
|
|
32070
|
-
|
|
32109
|
+
q: typeof args.q === "string" ? args.q : void 0
|
|
32110
|
+
};
|
|
32111
|
+
if (args.all === true)
|
|
32112
|
+
return { ok: true, data: { templates: await this.client.listAllTemplates(query), nextCursor: null }, meta: meta4 };
|
|
32113
|
+
const page = await this.client.listTemplatesPage({
|
|
32114
|
+
...query,
|
|
32115
|
+
limit: typeof args.limit === "number" ? args.limit : void 0,
|
|
32116
|
+
cursor: typeof args.cursor === "string" ? args.cursor : void 0
|
|
32071
32117
|
});
|
|
32072
|
-
return { ok: true, data:
|
|
32118
|
+
return { ok: true, data: page, meta: meta4 };
|
|
32073
32119
|
}
|
|
32074
32120
|
if (toolName === "get_template") {
|
|
32075
32121
|
const template = await this.client.getTemplate(String(args.templateId));
|
|
@@ -32277,6 +32323,7 @@ var init_src5 = __esm({
|
|
|
32277
32323
|
objectSchema = { type: "object", properties: {}, additionalProperties: true };
|
|
32278
32324
|
slideshowToolRegistry = [
|
|
32279
32325
|
{ name: "list_runs", title: "List slideshow runs", description: "List owner-scoped Slideshow Runs with optional filters.", effect: "read-only", inputSchema: { ...objectSchema, properties: { q: { type: "string" }, platform: { type: "string" }, profileId: { type: "string" }, brandProfileId: { type: "string" }, contentSlug: { type: "string" }, status: { type: "string" } } }, outputSchema: objectSchema },
|
|
32326
|
+
{ name: "recommend_templates", title: "Recommend slideshow page templates", description: "Before create_run: send the Page Contracts (role, density, slotSummary per page) and get a shortlist per page with reasons. Recently used Layout Families are demoted and one unused family is always kept. Pick one templateId per page yourself; gaps list pages needing more templates.", effect: "read-only", inputSchema: { ...objectSchema, properties: { platform: { type: "string" }, canvasPreset: { type: "string" }, pages: { type: "array", items: { type: "object", properties: { role: { type: "string" }, density: { type: "string" }, slotSummary: { type: "object" }, requiresImage: { type: "boolean" } }, required: ["role"] } }, k: { type: "integer", minimum: 2, maximum: 5 }, seed: { type: "string" } }, required: ["platform", "pages"] }, outputSchema: objectSchema },
|
|
32280
32327
|
{ name: "create_run", title: "Create slideshow run", description: "Create an ADR-0026 Slideshow Run from a ContentPack.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { pack: { type: "object" }, brandId: { type: "string" }, brandProfileId: { type: "string" }, contentSlug: { type: "string" } }, required: ["pack"] }, outputSchema: objectSchema },
|
|
32281
32328
|
{ name: "get_run", title: "Get slideshow run", description: "Get an owner-scoped Slideshow Run.", effect: "read-only", inputSchema: { ...objectSchema, properties: { runId: { type: "string" } }, required: ["runId"] }, outputSchema: objectSchema },
|
|
32282
32329
|
{ name: "update_run", title: "Update slideshow run", description: "Edit page-instance slots/layout/style/decorations or presentation preference with optimistic revision.", effect: "local-write-job", inputSchema: { ...objectSchema, properties: { runId: { type: "string" }, pack: { type: "object" }, brandId: { type: "string" }, brandProfileId: { type: "string" }, contentSlug: { type: "string" }, baseRevision: { type: "integer", minimum: 1 }, pageIndex: { type: "integer", minimum: 0 }, slots: { type: "object" }, layout: { type: "object" }, style: { type: "object" }, decorations: { type: "array" } }, required: ["runId"] }, outputSchema: objectSchema },
|
|
@@ -32305,6 +32352,8 @@ var init_src5 = __esm({
|
|
|
32305
32352
|
contentSlug: args.contentSlug,
|
|
32306
32353
|
status: args.status
|
|
32307
32354
|
}), meta: m };
|
|
32355
|
+
case "recommend_templates":
|
|
32356
|
+
return { ok: true, data: await this.client.recommendSlideshowTemplates({ platform: String(args.platform), pages: args.pages, canvasPreset: args.canvasPreset, k: args.k, seed: args.seed }), meta: m };
|
|
32308
32357
|
case "create_run":
|
|
32309
32358
|
return { ok: true, data: await this.client.createSlideshowRun({ pack: args.pack, brandId: args.brandId, brandProfileId: args.brandProfileId, contentSlug: args.contentSlug }), meta: m };
|
|
32310
32359
|
case "get_run":
|
|
@@ -32376,6 +32425,7 @@ var init_src6 = __esm({
|
|
|
32376
32425
|
};
|
|
32377
32426
|
slideshowNames = {
|
|
32378
32427
|
list_runs: "list_slideshow_runs",
|
|
32428
|
+
recommend_templates: "recommend_slideshow_templates",
|
|
32379
32429
|
create_run: "create_slideshow_run",
|
|
32380
32430
|
get_run: "get_slideshow_run",
|
|
32381
32431
|
update_run: "update_slideshow_run",
|
|
@@ -32508,9 +32558,9 @@ var init_v1_mcp = __esm({
|
|
|
32508
32558
|
// src/v1-cli.ts
|
|
32509
32559
|
init_src2();
|
|
32510
32560
|
import { cp, readFile, writeFile, mkdir, chmod, copyFile, rm } from "fs/promises";
|
|
32511
|
-
import { existsSync as
|
|
32512
|
-
import { homedir as
|
|
32513
|
-
import { join as
|
|
32561
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
32562
|
+
import { homedir as homedir4 } from "os";
|
|
32563
|
+
import { join as join6, resolve as resolve2 } from "path";
|
|
32514
32564
|
import { Command } from "commander";
|
|
32515
32565
|
|
|
32516
32566
|
// src/runtime/mcp-hosts.ts
|
|
@@ -33190,7 +33240,7 @@ function validateRun(moduleUrl, args) {
|
|
|
33190
33240
|
|
|
33191
33241
|
// src/studioIdentity.ts
|
|
33192
33242
|
var FIXTURE_WORKSPACE_IDS = /* @__PURE__ */ new Set(["workspace_local"]);
|
|
33193
|
-
var IDENTITY_CHAIN_HINT = "
|
|
33243
|
+
var IDENTITY_CHAIN_HINT = "Run `easysociable auth login` (approve in the browser you use for Studio) \u2192 create Run \u2192 open studioUrl as the same account. See docs/operations/studio-identity-chain.md";
|
|
33194
33244
|
async function assertHumanStudioIdentity(client, options = {}) {
|
|
33195
33245
|
if (options.allowLocalFixture === true || process.env.EASYSOCIABLE_ALLOW_LOCAL_FIXTURE === "1")
|
|
33196
33246
|
return;
|
|
@@ -33203,25 +33253,329 @@ async function assertHumanStudioIdentity(client, options = {}) {
|
|
|
33203
33253
|
}
|
|
33204
33254
|
}
|
|
33205
33255
|
|
|
33256
|
+
// src/browserLogin.ts
|
|
33257
|
+
import { spawn as spawn2 } from "child_process";
|
|
33258
|
+
import { randomBytes } from "crypto";
|
|
33259
|
+
import { createServer } from "http";
|
|
33260
|
+
var DONE_HTML = '<!doctype html><meta charset="utf-8"><title>EasySociable</title><body style="font-family:system-ui;text-align:center;padding:64px"><h1>Agent connected</h1><p>You can close this tab and return to your Agent.</p></body>';
|
|
33261
|
+
function openBrowser(url2) {
|
|
33262
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url2]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : ["xdg-open", [url2]];
|
|
33263
|
+
try {
|
|
33264
|
+
spawn2(cmd, args, { stdio: "ignore", detached: true }).on("error", () => {
|
|
33265
|
+
}).unref();
|
|
33266
|
+
} catch {
|
|
33267
|
+
}
|
|
33268
|
+
}
|
|
33269
|
+
function waitForBrowserLogin(options) {
|
|
33270
|
+
const state = randomBytes(16).toString("hex");
|
|
33271
|
+
return new Promise((resolve3, reject) => {
|
|
33272
|
+
const server = createServer((req, res) => {
|
|
33273
|
+
if (req.method !== "POST" || !req.url?.startsWith("/callback")) {
|
|
33274
|
+
res.writeHead(404).end();
|
|
33275
|
+
return;
|
|
33276
|
+
}
|
|
33277
|
+
let body = "";
|
|
33278
|
+
req.on("data", (chunk) => {
|
|
33279
|
+
body += chunk;
|
|
33280
|
+
});
|
|
33281
|
+
req.on("end", () => {
|
|
33282
|
+
const form = new URLSearchParams(body);
|
|
33283
|
+
const apiKey = form.get("apiKey") ?? "";
|
|
33284
|
+
if (form.get("state") !== state || !apiKey.startsWith("es_")) {
|
|
33285
|
+
res.writeHead(400, { "content-type": "text/plain" }).end("Invalid login callback");
|
|
33286
|
+
return;
|
|
33287
|
+
}
|
|
33288
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(DONE_HTML);
|
|
33289
|
+
finish(null, { apiKey, gatewayUrl: (form.get("gatewayUrl") ?? "").replace(/\/$/, "") });
|
|
33290
|
+
});
|
|
33291
|
+
});
|
|
33292
|
+
const timer = setTimeout(() => finish(new Error("Browser login timed out. Re-run `easysociable auth login`.")), options.timeoutMs ?? 5 * 6e4);
|
|
33293
|
+
function finish(error52, result) {
|
|
33294
|
+
clearTimeout(timer);
|
|
33295
|
+
server.close();
|
|
33296
|
+
if (error52) reject(error52);
|
|
33297
|
+
else resolve3(result);
|
|
33298
|
+
}
|
|
33299
|
+
server.on("error", (error52) => finish(error52));
|
|
33300
|
+
server.listen(0, "127.0.0.1", () => {
|
|
33301
|
+
const { port } = server.address();
|
|
33302
|
+
const url2 = new URL("/cli/authorize", options.siteUrl);
|
|
33303
|
+
url2.searchParams.set("port", String(port));
|
|
33304
|
+
url2.searchParams.set("state", state);
|
|
33305
|
+
options.onReady(url2.toString());
|
|
33306
|
+
});
|
|
33307
|
+
});
|
|
33308
|
+
}
|
|
33309
|
+
|
|
33310
|
+
// src/formulas.ts
|
|
33311
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
33312
|
+
import { homedir as homedir3 } from "os";
|
|
33313
|
+
import { join as join4 } from "path";
|
|
33314
|
+
var DEFAULT_CANDIDATES = [
|
|
33315
|
+
process.env.EASYSOCIABLE_FORMULA_CATALOG,
|
|
33316
|
+
join4(homedir3(), "Documents", "Obsidian Vault", "TikTok Radar", "graph")
|
|
33317
|
+
].filter((p) => Boolean(p));
|
|
33318
|
+
function resolveCatalogDir(explicit) {
|
|
33319
|
+
if (explicit) {
|
|
33320
|
+
if (!existsSync4(join4(explicit, "catalog.json"))) {
|
|
33321
|
+
throw new Error(`No catalog.json under ${explicit}`);
|
|
33322
|
+
}
|
|
33323
|
+
return explicit;
|
|
33324
|
+
}
|
|
33325
|
+
for (const candidate of DEFAULT_CANDIDATES) {
|
|
33326
|
+
if (existsSync4(join4(candidate, "catalog.json"))) return candidate;
|
|
33327
|
+
}
|
|
33328
|
+
throw new Error(
|
|
33329
|
+
`Formula catalog not found. Set EASYSOCIABLE_FORMULA_CATALOG to the graph/ dir (expected catalog.json). Tried: ${DEFAULT_CANDIDATES.join(", ")}`
|
|
33330
|
+
);
|
|
33331
|
+
}
|
|
33332
|
+
function readJson(path) {
|
|
33333
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
33334
|
+
}
|
|
33335
|
+
function loadCatalog(dir) {
|
|
33336
|
+
const root = resolveCatalogDir(dir);
|
|
33337
|
+
return readJson(join4(root, "catalog.json"));
|
|
33338
|
+
}
|
|
33339
|
+
function listFormulas(opts = {}) {
|
|
33340
|
+
const root = resolveCatalogDir(opts.dir);
|
|
33341
|
+
const formulasDir = join4(root, "formulas");
|
|
33342
|
+
if (!existsSync4(formulasDir)) return [];
|
|
33343
|
+
const cards = [];
|
|
33344
|
+
for (const name of readdirSync2(formulasDir)) {
|
|
33345
|
+
if (!name.startsWith("F-") || !name.endsWith(".json")) continue;
|
|
33346
|
+
const card = readJson(join4(formulasDir, name));
|
|
33347
|
+
if (opts.recipe && card.recipe !== opts.recipe) continue;
|
|
33348
|
+
if (opts.niche && card.niche !== opts.niche) continue;
|
|
33349
|
+
if (opts.status && card.status !== opts.status) continue;
|
|
33350
|
+
if (opts.platform && card.platform !== opts.platform) continue;
|
|
33351
|
+
cards.push(card);
|
|
33352
|
+
}
|
|
33353
|
+
return cards.sort((a, b) => {
|
|
33354
|
+
const statusRank = (s) => s === "published" ? 0 : 1;
|
|
33355
|
+
const d = statusRank(a.status) - statusRank(b.status);
|
|
33356
|
+
if (d !== 0) return d;
|
|
33357
|
+
return (b.evidence.account_count || 0) - (a.evidence.account_count || 0);
|
|
33358
|
+
});
|
|
33359
|
+
}
|
|
33360
|
+
function getFormula(id, dir) {
|
|
33361
|
+
const root = resolveCatalogDir(dir);
|
|
33362
|
+
const path = join4(root, "formulas", `${id}.json`);
|
|
33363
|
+
if (!existsSync4(path)) throw new Error(`Formula not found: ${id}`);
|
|
33364
|
+
const looksPath = join4(root, "looks.json");
|
|
33365
|
+
const looks = existsSync4(looksPath) ? readJson(looksPath)[id] : void 0;
|
|
33366
|
+
return { ...readJson(path), looks: looks ?? [] };
|
|
33367
|
+
}
|
|
33368
|
+
async function fetchHostedFormula(id, baseUrl, fetchImpl = fetch) {
|
|
33369
|
+
const response = await fetchImpl(`${baseUrl.replace(/\/$/, "")}/v1/formulas/${encodeURIComponent(id)}`);
|
|
33370
|
+
const body = await response.json().catch(() => null);
|
|
33371
|
+
if (!response.ok || !body?.ok || !body.data) {
|
|
33372
|
+
throw new Error(`Formula not found: ${id} (${body?.error?.message ?? `HTTP ${response.status}`})`);
|
|
33373
|
+
}
|
|
33374
|
+
return body.data;
|
|
33375
|
+
}
|
|
33376
|
+
async function resolveFormula(id, opts) {
|
|
33377
|
+
if (opts.dir) return getFormula(id, opts.dir);
|
|
33378
|
+
try {
|
|
33379
|
+
return getFormula(id);
|
|
33380
|
+
} catch {
|
|
33381
|
+
return fetchHostedFormula(id, opts.baseUrl, opts.fetchImpl);
|
|
33382
|
+
}
|
|
33383
|
+
}
|
|
33384
|
+
function recommendFormulas(input) {
|
|
33385
|
+
const limit = input.limit ?? 5;
|
|
33386
|
+
const all = listFormulas({
|
|
33387
|
+
dir: input.dir,
|
|
33388
|
+
recipe: input.recipe,
|
|
33389
|
+
platform: input.platform
|
|
33390
|
+
});
|
|
33391
|
+
const scored = all.map((card) => {
|
|
33392
|
+
let score = 0;
|
|
33393
|
+
const reasons = [];
|
|
33394
|
+
if (card.recipe === input.recipe) {
|
|
33395
|
+
score += 5;
|
|
33396
|
+
reasons.push("recipe match");
|
|
33397
|
+
}
|
|
33398
|
+
if (input.niche && card.niche === input.niche) {
|
|
33399
|
+
score += 2;
|
|
33400
|
+
reasons.push("niche match");
|
|
33401
|
+
} else if (input.niche && card.fit?.length) {
|
|
33402
|
+
score += 0.5;
|
|
33403
|
+
reasons.push("cross-niche kept");
|
|
33404
|
+
}
|
|
33405
|
+
if (card.status === "published") {
|
|
33406
|
+
score += 2;
|
|
33407
|
+
reasons.push(`published (${card.evidence.account_count} accounts)`);
|
|
33408
|
+
} else {
|
|
33409
|
+
reasons.push("hypothesis \u2014 weak evidence");
|
|
33410
|
+
}
|
|
33411
|
+
const points = card.beats.filter((b) => b.role === "point").length;
|
|
33412
|
+
if (typeof input.factCount === "number" && input.factCount > 0) {
|
|
33413
|
+
if (input.factCount >= points) {
|
|
33414
|
+
score += 1.5;
|
|
33415
|
+
reasons.push("enough facts for point beats");
|
|
33416
|
+
} else {
|
|
33417
|
+
score -= 1;
|
|
33418
|
+
reasons.push(`need ~${points} facts, have ${input.factCount}`);
|
|
33419
|
+
}
|
|
33420
|
+
}
|
|
33421
|
+
const newest = card.evidence.newest_days_ago;
|
|
33422
|
+
if (typeof newest === "number" && newest <= 60) {
|
|
33423
|
+
score += 0.5;
|
|
33424
|
+
reasons.push("recent evidence");
|
|
33425
|
+
}
|
|
33426
|
+
score += Math.min(card.evidence.account_count, 5) * 0.3;
|
|
33427
|
+
return { ...card, score, reasons };
|
|
33428
|
+
});
|
|
33429
|
+
scored.sort((a, b) => b.score - a.score);
|
|
33430
|
+
const top = scored.slice(0, limit);
|
|
33431
|
+
const best = top[0]?.score ?? 0;
|
|
33432
|
+
if (!top.length || best < 4) {
|
|
33433
|
+
return {
|
|
33434
|
+
cards: top,
|
|
33435
|
+
stop: true,
|
|
33436
|
+
message: "Top score is low \u2014 change the angle or recipe rather than forcing a card."
|
|
33437
|
+
};
|
|
33438
|
+
}
|
|
33439
|
+
return { cards: top, stop: false };
|
|
33440
|
+
}
|
|
33441
|
+
|
|
33442
|
+
// src/hooks.ts
|
|
33443
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
33444
|
+
import { join as join5 } from "path";
|
|
33445
|
+
function readJson2(path) {
|
|
33446
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
33447
|
+
}
|
|
33448
|
+
function loadHooksCatalog(dir) {
|
|
33449
|
+
const root = resolveCatalogDir(dir);
|
|
33450
|
+
const path = join5(root, "hooks_catalog.json");
|
|
33451
|
+
if (!existsSync5(path)) {
|
|
33452
|
+
throw new Error(`No hooks_catalog.json under ${root}. Run: python3 scripts/compile_hooks.py`);
|
|
33453
|
+
}
|
|
33454
|
+
return readJson2(path);
|
|
33455
|
+
}
|
|
33456
|
+
function listHooks(opts = {}) {
|
|
33457
|
+
const root = resolveCatalogDir(opts.dir);
|
|
33458
|
+
const hooksDir = join5(root, "hooks");
|
|
33459
|
+
if (!existsSync5(hooksDir))
|
|
33460
|
+
return [];
|
|
33461
|
+
const cards = [];
|
|
33462
|
+
for (const name of readdirSync3(hooksDir)) {
|
|
33463
|
+
if (!name.startsWith("H-") || !name.endsWith(".json"))
|
|
33464
|
+
continue;
|
|
33465
|
+
const card = readJson2(join5(hooksDir, name));
|
|
33466
|
+
if (opts.type && card.hook_type !== opts.type)
|
|
33467
|
+
continue;
|
|
33468
|
+
if (opts.niche && card.niche !== opts.niche)
|
|
33469
|
+
continue;
|
|
33470
|
+
if (opts.status && card.status !== opts.status)
|
|
33471
|
+
continue;
|
|
33472
|
+
if (opts.formula && !(card.formula_ids || []).includes(opts.formula))
|
|
33473
|
+
continue;
|
|
33474
|
+
cards.push(card);
|
|
33475
|
+
}
|
|
33476
|
+
return cards.sort((a, b) => {
|
|
33477
|
+
const statusRank = (s) => s === "published" ? 0 : 1;
|
|
33478
|
+
const d = statusRank(a.status) - statusRank(b.status);
|
|
33479
|
+
if (d !== 0)
|
|
33480
|
+
return d;
|
|
33481
|
+
return (b.evidence.account_count || 0) - (a.evidence.account_count || 0);
|
|
33482
|
+
});
|
|
33483
|
+
}
|
|
33484
|
+
function getHook(id, dir) {
|
|
33485
|
+
const root = resolveCatalogDir(dir);
|
|
33486
|
+
const path = join5(root, "hooks", `${id}.json`);
|
|
33487
|
+
if (!existsSync5(path))
|
|
33488
|
+
throw new Error(`Hook not found: ${id}`);
|
|
33489
|
+
return readJson2(path);
|
|
33490
|
+
}
|
|
33491
|
+
async function fetchHostedHook(id, baseUrl, fetchImpl = fetch) {
|
|
33492
|
+
const response = await fetchImpl(`${baseUrl.replace(/\/$/, "")}/v1/hooks/${encodeURIComponent(id)}`);
|
|
33493
|
+
const body = await response.json().catch(() => null);
|
|
33494
|
+
if (!response.ok || !body?.ok || !body.data) {
|
|
33495
|
+
throw new Error(`Hook not found: ${id} (${body?.error?.message ?? `HTTP ${response.status}`})`);
|
|
33496
|
+
}
|
|
33497
|
+
return body.data;
|
|
33498
|
+
}
|
|
33499
|
+
async function resolveHook(id, opts) {
|
|
33500
|
+
if (opts.dir)
|
|
33501
|
+
return getHook(id, opts.dir);
|
|
33502
|
+
try {
|
|
33503
|
+
return getHook(id);
|
|
33504
|
+
} catch {
|
|
33505
|
+
return fetchHostedHook(id, opts.baseUrl, opts.fetchImpl);
|
|
33506
|
+
}
|
|
33507
|
+
}
|
|
33508
|
+
function recommendHooks(input) {
|
|
33509
|
+
const limit = input.limit ?? 5;
|
|
33510
|
+
const all = listHooks({
|
|
33511
|
+
dir: input.dir,
|
|
33512
|
+
type: input.type
|
|
33513
|
+
});
|
|
33514
|
+
const topic = (input.topic || "").toLowerCase();
|
|
33515
|
+
const scored = all.map((card) => {
|
|
33516
|
+
let score = 0;
|
|
33517
|
+
const reasons = [];
|
|
33518
|
+
if (input.type && card.hook_type === input.type) {
|
|
33519
|
+
score += 3;
|
|
33520
|
+
reasons.push("hook_type match");
|
|
33521
|
+
}
|
|
33522
|
+
if (input.niche && card.niche === input.niche) {
|
|
33523
|
+
score += 2;
|
|
33524
|
+
reasons.push("niche match");
|
|
33525
|
+
} else if (input.niche) {
|
|
33526
|
+
score += 0.25;
|
|
33527
|
+
reasons.push("cross-niche kept");
|
|
33528
|
+
}
|
|
33529
|
+
if (card.status === "published") {
|
|
33530
|
+
score += 2;
|
|
33531
|
+
reasons.push(`published (${card.evidence.account_count} accounts)`);
|
|
33532
|
+
} else {
|
|
33533
|
+
reasons.push("hypothesis \u2014 weak evidence");
|
|
33534
|
+
}
|
|
33535
|
+
const newest = card.evidence.newest_days_ago;
|
|
33536
|
+
if (typeof newest === "number" && newest <= 60) {
|
|
33537
|
+
score += 0.5;
|
|
33538
|
+
reasons.push("recent evidence");
|
|
33539
|
+
}
|
|
33540
|
+
score += Math.min(card.evidence.account_count, 5) * 0.3;
|
|
33541
|
+
if (topic && card.format.toLowerCase().includes("{x}")) {
|
|
33542
|
+
score += 0.25;
|
|
33543
|
+
reasons.push("format has {X} slot");
|
|
33544
|
+
}
|
|
33545
|
+
return { ...card, score, reasons };
|
|
33546
|
+
});
|
|
33547
|
+
scored.sort((a, b) => b.score - a.score);
|
|
33548
|
+
const top = scored.slice(0, limit);
|
|
33549
|
+
const best = top[0]?.score ?? 0;
|
|
33550
|
+
if (!top.length || best < 2) {
|
|
33551
|
+
return {
|
|
33552
|
+
cards: top,
|
|
33553
|
+
stop: true,
|
|
33554
|
+
message: "Top score is low \u2014 try another niche/type or gather more labeled hooks."
|
|
33555
|
+
};
|
|
33556
|
+
}
|
|
33557
|
+
return { cards: top, stop: false };
|
|
33558
|
+
}
|
|
33559
|
+
|
|
33206
33560
|
// src/v1-cli.ts
|
|
33207
33561
|
var CLI_MODULE_URL = import.meta.url;
|
|
33208
33562
|
function readCliVersion() {
|
|
33209
33563
|
try {
|
|
33210
|
-
const pkg = JSON.parse(
|
|
33564
|
+
const pkg = JSON.parse(readFileSync5(join6(cliPackageRoot(CLI_MODULE_URL), "package.json"), "utf8"));
|
|
33211
33565
|
return typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
33212
33566
|
} catch {
|
|
33213
33567
|
return "0.0.0";
|
|
33214
33568
|
}
|
|
33215
33569
|
}
|
|
33216
33570
|
var HEADLESS_RENDER_DISABLED = "PNG delivery is Studio Download (browser ZIP). CLI/MCP hosted PNG jobs are retired. Open the Run in Studio and use Download.";
|
|
33217
|
-
var credentialPath =
|
|
33571
|
+
var credentialPath = join6(homedir4(), ".config", "easysociable", "credentials.json");
|
|
33218
33572
|
function resolveInstallSkillIds(options) {
|
|
33219
33573
|
return parseInstallSkills(typeof options.skills === "string" ? options.skills : void 0);
|
|
33220
33574
|
}
|
|
33221
33575
|
function readStoredCredentials() {
|
|
33222
33576
|
try {
|
|
33223
|
-
if (!
|
|
33224
|
-
const parsed = JSON.parse(
|
|
33577
|
+
if (!existsSync6(credentialPath)) return null;
|
|
33578
|
+
const parsed = JSON.parse(readFileSync5(credentialPath, "utf8"));
|
|
33225
33579
|
if (typeof parsed.apiKey !== "string" || typeof parsed.baseUrl !== "string") return null;
|
|
33226
33580
|
return { apiKey: parsed.apiKey, baseUrl: parsed.baseUrl };
|
|
33227
33581
|
} catch {
|
|
@@ -33229,7 +33583,7 @@ function readStoredCredentials() {
|
|
|
33229
33583
|
}
|
|
33230
33584
|
}
|
|
33231
33585
|
async function writeStoredCredentials(credentials) {
|
|
33232
|
-
await mkdir(
|
|
33586
|
+
await mkdir(join6(homedir4(), ".config", "easysociable"), { recursive: true, mode: 448 });
|
|
33233
33587
|
await writeFile(credentialPath, `${JSON.stringify(credentials, null, 2)}
|
|
33234
33588
|
`, { mode: 384 });
|
|
33235
33589
|
await chmod(credentialPath, 384);
|
|
@@ -33252,10 +33606,13 @@ function commandWantsJson(command) {
|
|
|
33252
33606
|
const withGlobals = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {};
|
|
33253
33607
|
return withGlobals.json === true;
|
|
33254
33608
|
}
|
|
33609
|
+
function gatewayBaseUrl(stored = readStoredCredentials()) {
|
|
33610
|
+
return stored?.baseUrl ?? process.env.EASYSOCIABLE_GATEWAY_URL ?? process.env.EASYSOCIABLE_API_URL ?? "http://127.0.0.1:8787";
|
|
33611
|
+
}
|
|
33255
33612
|
function clientFromEnv() {
|
|
33256
33613
|
const stored = readStoredCredentials();
|
|
33257
33614
|
const allowEnv = process.env.EASYSOCIABLE_ALLOW_ENV_CREDENTIALS === "true";
|
|
33258
|
-
const baseUrl = stored
|
|
33615
|
+
const baseUrl = gatewayBaseUrl(stored);
|
|
33259
33616
|
const apiKey = stored?.apiKey ?? (allowEnv ? process.env.EASYSOCIABLE_API_KEY : void 0);
|
|
33260
33617
|
if (!apiKey) throw new Error("EASYSOCIABLE_API_KEY is required. Run `easysociable auth login` first.");
|
|
33261
33618
|
return new TemplateGatewayClient({
|
|
@@ -33270,14 +33627,14 @@ function jsonOption(command) {
|
|
|
33270
33627
|
}
|
|
33271
33628
|
function defaultEngineRoot() {
|
|
33272
33629
|
try {
|
|
33273
|
-
if (
|
|
33630
|
+
if (existsSync6("/workspace") && statSync2("/workspace").isDirectory())
|
|
33274
33631
|
return "/workspace";
|
|
33275
33632
|
} catch {
|
|
33276
33633
|
}
|
|
33277
33634
|
return process.cwd();
|
|
33278
33635
|
}
|
|
33279
33636
|
function installHome(options) {
|
|
33280
|
-
return typeof options.home === "string" && options.home ? String(options.home) :
|
|
33637
|
+
return typeof options.home === "string" && options.home ? String(options.home) : homedir4();
|
|
33281
33638
|
}
|
|
33282
33639
|
function installSource(options) {
|
|
33283
33640
|
if (typeof options.source === "string" && options.source)
|
|
@@ -33288,17 +33645,17 @@ function installSource(options) {
|
|
|
33288
33645
|
}
|
|
33289
33646
|
function detectedHosts(home) {
|
|
33290
33647
|
const candidates = [
|
|
33291
|
-
{ host: "codex", dir:
|
|
33292
|
-
{ host: "claude", dir:
|
|
33648
|
+
{ host: "codex", dir: join6(home, ".codex") },
|
|
33649
|
+
{ host: "claude", dir: join6(home, ".claude") }
|
|
33293
33650
|
];
|
|
33294
|
-
return candidates.filter((item) =>
|
|
33651
|
+
return candidates.filter((item) => existsSync6(item.dir)).map((item) => ({ host: item.host, configPath: join6(item.dir, "mcp.json"), skillsPath: join6(item.dir, "skills") }));
|
|
33295
33652
|
}
|
|
33296
33653
|
async function planInstallation(options) {
|
|
33297
33654
|
const home = installHome(options);
|
|
33298
33655
|
const source = installSource(options);
|
|
33299
33656
|
const hosts = detectedHosts(home);
|
|
33300
33657
|
const skillNames = resolveInstallSkillIds(options);
|
|
33301
|
-
const missingSkills = skillNames.filter((name) => !
|
|
33658
|
+
const missingSkills = skillNames.filter((name) => !existsSync6(join6(source, name)));
|
|
33302
33659
|
return {
|
|
33303
33660
|
source,
|
|
33304
33661
|
home,
|
|
@@ -33307,7 +33664,7 @@ async function planInstallation(options) {
|
|
|
33307
33664
|
missingSkills,
|
|
33308
33665
|
changes: hosts.flatMap((host) => [
|
|
33309
33666
|
`merge ${host.configPath} with one easysociable MCP registration`,
|
|
33310
|
-
...skillNames.map((name) => `install ${
|
|
33667
|
+
...skillNames.map((name) => `install ${join6(host.skillsPath, name)}`)
|
|
33311
33668
|
])
|
|
33312
33669
|
};
|
|
33313
33670
|
}
|
|
@@ -33323,7 +33680,7 @@ async function applyInstallation(options) {
|
|
|
33323
33680
|
const backupSuffix = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
33324
33681
|
const backups = [];
|
|
33325
33682
|
for (const host of detectedHosts(String(plan.home))) {
|
|
33326
|
-
if (
|
|
33683
|
+
if (existsSync6(host.configPath)) {
|
|
33327
33684
|
const backupPath = `${host.configPath}.bak.${backupSuffix}`;
|
|
33328
33685
|
await copyFile(host.configPath, backupPath);
|
|
33329
33686
|
backups.push(backupPath);
|
|
@@ -33331,7 +33688,7 @@ async function applyInstallation(options) {
|
|
|
33331
33688
|
await installHostConfig({ host: host.host, dataDir: "", configPath: host.configPath });
|
|
33332
33689
|
await mkdir(host.skillsPath, { recursive: true, mode: 448 });
|
|
33333
33690
|
for (const skill of resolveInstallSkillIds(options))
|
|
33334
|
-
await cp(
|
|
33691
|
+
await cp(join6(String(plan.source), skill), join6(host.skillsPath, skill), { recursive: true, force: false, errorOnExist: false });
|
|
33335
33692
|
}
|
|
33336
33693
|
if (options.json !== true) {
|
|
33337
33694
|
process.stdout.write(`Installed skills your Agent will use:
|
|
@@ -33478,12 +33835,23 @@ function buildV1Cli() {
|
|
|
33478
33835
|
jsonOption(auth.command("status").description("Show authentication status")).action(async (...args) => {
|
|
33479
33836
|
const command = args.at(-1);
|
|
33480
33837
|
const stored = readStoredCredentials();
|
|
33481
|
-
print({ authenticated: Boolean(stored), baseUrl: stored
|
|
33838
|
+
print({ authenticated: Boolean(stored), baseUrl: gatewayBaseUrl(stored), credentialPath }, Boolean(command.opts().json));
|
|
33482
33839
|
});
|
|
33483
|
-
auth.command("login").description("
|
|
33840
|
+
auth.command("login").description("Sign in via the browser (or store an existing API key with --api-key)").option("--api-key <key>").option("--gateway-url <url>", "Gateway URL").option("--site-url <url>", "EasySociable site URL", process.env.EASYSOCIABLE_SITE_URL ?? "https://easysociable.com").action(async (...args) => {
|
|
33484
33841
|
const command = args.at(-1);
|
|
33485
33842
|
const opts = command.opts();
|
|
33486
|
-
|
|
33843
|
+
const login = opts.apiKey ? { apiKey: opts.apiKey, gatewayUrl: "" } : await waitForBrowserLogin({
|
|
33844
|
+
siteUrl: opts.siteUrl,
|
|
33845
|
+
onReady: (url2) => {
|
|
33846
|
+
process.stdout.write(`Opening your browser to connect this Agent:
|
|
33847
|
+
${url2}
|
|
33848
|
+
Waiting for approval\u2026
|
|
33849
|
+
`);
|
|
33850
|
+
openBrowser(url2);
|
|
33851
|
+
}
|
|
33852
|
+
});
|
|
33853
|
+
const baseUrl = (opts.gatewayUrl ?? (login.gatewayUrl || gatewayBaseUrl(null))).replace(/\/$/, "");
|
|
33854
|
+
await writeStoredCredentials({ apiKey: login.apiKey, baseUrl });
|
|
33487
33855
|
process.stdout.write(`Stored EasySociable credentials at ${credentialPath}.
|
|
33488
33856
|
`);
|
|
33489
33857
|
});
|
|
@@ -33503,7 +33871,20 @@ function buildV1Cli() {
|
|
|
33503
33871
|
jsonOption(images.command("search").requiredOption("--q <query>")).action(withClient(async (client, opts) => client.requestJson("GET", `/v1/images?q=${encodeURIComponent(String(opts.q))}`)));
|
|
33504
33872
|
jsonOption(images.command("attest").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.requestJson("POST", `/v1/images/${opts.id}/attest`, { accepted: true })));
|
|
33505
33873
|
const templates = program.command("templates").description("Create and manage Page Template drafts");
|
|
33506
|
-
jsonOption(templates.command("list")).action(withClient(async (client) =>
|
|
33874
|
+
jsonOption(templates.command("list").option("--platform <platform>", "tiktok | instagram | linkedin | threads | xiaohongshu").option("--canvas <preset>", "Canvas preset e.g. 9:16, 4:5").option("--role <role>", "Page role filter (weak tag)").option("--limit <n>", "Page size (max 50)").option("--cursor <cursor>", "Offset cursor from a previous page").option("--all", "Return every matching template (drains pages)")).action(withClient(async (client, opts) => {
|
|
33875
|
+
const query = {
|
|
33876
|
+
platforms: opts.platform ? [String(opts.platform)] : void 0,
|
|
33877
|
+
canvasPreset: opts.canvas ? String(opts.canvas) : void 0,
|
|
33878
|
+
roles: opts.role ? [String(opts.role)] : void 0
|
|
33879
|
+
};
|
|
33880
|
+
if (opts.all === true)
|
|
33881
|
+
return client.listAllTemplates(query);
|
|
33882
|
+
const page = await client.listTemplatesPage({ ...query, limit: opts.limit ? Number(opts.limit) : void 0, cursor: opts.cursor ? String(opts.cursor) : void 0 });
|
|
33883
|
+
if (page.nextCursor)
|
|
33884
|
+
process.stderr.write(`More templates: --cursor ${page.nextCursor} or --all
|
|
33885
|
+
`);
|
|
33886
|
+
return page.templates;
|
|
33887
|
+
}));
|
|
33507
33888
|
jsonOption(templates.command("get").requiredOption("--id <id>")).action(withClient(async (client, opts) => client.getTemplate(String(opts.id))));
|
|
33508
33889
|
jsonOption(templates.command("create-draft").requiredOption("--file <path>")).action(withClient(async (client, opts) => client.createDraft(JSON.parse(await readFile(String(opts.file), "utf8")))));
|
|
33509
33890
|
jsonOption(templates.command("update-draft").requiredOption("--id <id>").requiredOption("--file <path>")).action(withClient(async (client, opts) => client.updateDraft(String(opts.id), JSON.parse(await readFile(String(opts.file), "utf8")))));
|
|
@@ -33531,6 +33912,10 @@ function buildV1Cli() {
|
|
|
33531
33912
|
contentSlug: opts.contentSlug ? String(opts.contentSlug) : void 0,
|
|
33532
33913
|
status: opts.status ? String(opts.status) : void 0
|
|
33533
33914
|
})));
|
|
33915
|
+
jsonOption(slideshows.command("recommend").description("Shortlist Page Templates per page from Page Contracts JSON ({ platform, pages: [{ role, density?, slotSummary? }], seed? })").requiredOption("--file <path>").option("--seed <seed>", "Tie-break seed, e.g. the content slug")).action(withClient(async (client, opts) => {
|
|
33916
|
+
const input = JSON.parse(await readFile(String(opts.file), "utf8"));
|
|
33917
|
+
return client.recommendSlideshowTemplates(opts.seed ? { ...input, seed: String(opts.seed) } : input);
|
|
33918
|
+
}));
|
|
33534
33919
|
jsonOption(slideshows.command("create").requiredOption("--file <path>").option("--allow-local-fixture", "Allow workspace_local fixture keys (dev only)")).action(withClient(async (client, opts) => {
|
|
33535
33920
|
await assertHumanStudioIdentity(client, { allowLocalFixture: opts.allowLocalFixture === true });
|
|
33536
33921
|
return client.createSlideshowRun({ pack: JSON.parse(await readFile(String(opts.file), "utf8")) });
|
|
@@ -33569,6 +33954,100 @@ function buildV1Cli() {
|
|
|
33569
33954
|
jsonOption(renders.command("export").description("Retired: PNG packs come from Studio Download").requiredOption("--id <id>").requiredOption("--output <path>")).action(withClient(async () => {
|
|
33570
33955
|
throw new Error(HEADLESS_RENDER_DISABLED);
|
|
33571
33956
|
}));
|
|
33957
|
+
const formulas = program.command("formulas").description("Read Formula cards (local TikTok Radar graph/; `get` falls back to the hosted catalog)");
|
|
33958
|
+
formulas.option("--catalog <path>", "Path to graph/ dir containing catalog.json (or set EASYSOCIABLE_FORMULA_CATALOG)");
|
|
33959
|
+
jsonOption(formulas.command("list").option("--recipe <recipe>").option("--niche <niche>").option("--status <status>").option("--platform <platform>")).action((...args) => {
|
|
33960
|
+
const command = args.at(-1);
|
|
33961
|
+
const opts = command.optsWithGlobals();
|
|
33962
|
+
const cards = listFormulas({
|
|
33963
|
+
dir: opts.catalog ? String(opts.catalog) : void 0,
|
|
33964
|
+
recipe: opts.recipe ? String(opts.recipe) : void 0,
|
|
33965
|
+
niche: opts.niche ? String(opts.niche) : void 0,
|
|
33966
|
+
status: opts.status ? String(opts.status) : void 0,
|
|
33967
|
+
platform: opts.platform ? String(opts.platform) : void 0
|
|
33968
|
+
});
|
|
33969
|
+
print(cards.map((c) => ({
|
|
33970
|
+
id: c.id,
|
|
33971
|
+
recipe: c.recipe,
|
|
33972
|
+
niche: c.niche,
|
|
33973
|
+
swipe: c.swipe,
|
|
33974
|
+
status: c.status,
|
|
33975
|
+
accounts: c.evidence.account_count,
|
|
33976
|
+
posts: c.evidence.post_count,
|
|
33977
|
+
title_template: c.title_template
|
|
33978
|
+
})), commandWantsJson(command));
|
|
33979
|
+
});
|
|
33980
|
+
jsonOption(formulas.command("get").requiredOption("--id <id>")).action(async (...args) => {
|
|
33981
|
+
const command = args.at(-1);
|
|
33982
|
+
const opts = command.optsWithGlobals();
|
|
33983
|
+
const card = await resolveFormula(String(opts.id), { dir: opts.catalog ? String(opts.catalog) : void 0, baseUrl: gatewayBaseUrl() });
|
|
33984
|
+
print(card, commandWantsJson(command));
|
|
33985
|
+
});
|
|
33986
|
+
jsonOption(formulas.command("catalog")).action((...args) => {
|
|
33987
|
+
const command = args.at(-1);
|
|
33988
|
+
const opts = command.optsWithGlobals();
|
|
33989
|
+
print(loadCatalog(opts.catalog ? String(opts.catalog) : void 0), commandWantsJson(command));
|
|
33990
|
+
});
|
|
33991
|
+
jsonOption(formulas.command("recommend").description("Rank cards for a recipe (niche is a bonus). Pass --recipe from Jev theme\u2192recipe.").requiredOption("--recipe <recipe>").option("--niche <niche>").option("--platform <platform>", "tiktok").option("--fact-count <n>", "How many user facts are available to fill point beats").option("--limit <n>", "Max cards", "5")).action((...args) => {
|
|
33992
|
+
const command = args.at(-1);
|
|
33993
|
+
const opts = command.optsWithGlobals();
|
|
33994
|
+
const result = recommendFormulas({
|
|
33995
|
+
recipe: String(opts.recipe),
|
|
33996
|
+
niche: opts.niche ? String(opts.niche) : void 0,
|
|
33997
|
+
platform: opts.platform ? String(opts.platform) : void 0,
|
|
33998
|
+
factCount: opts.factCount != null ? Number(opts.factCount) : void 0,
|
|
33999
|
+
dir: opts.catalog ? String(opts.catalog) : void 0,
|
|
34000
|
+
limit: opts.limit != null ? Number(opts.limit) : 5
|
|
34001
|
+
});
|
|
34002
|
+
print(result, commandWantsJson(command));
|
|
34003
|
+
});
|
|
34004
|
+
const hooks = program.command("hooks").description("Read Hook format molds (local graph/hooks/; `get` falls back to hosted catalog)");
|
|
34005
|
+
hooks.option("--catalog <path>", "Path to graph/ dir (or set EASYSOCIABLE_FORMULA_CATALOG)");
|
|
34006
|
+
jsonOption(hooks.command("list").option("--type <hook_type>", "contrast | list-tease | identity | number | other").option("--niche <niche>").option("--status <status>").option("--formula <formula_id>", "Only hooks soft-linked to this formula")).action((...args) => {
|
|
34007
|
+
const command = args.at(-1);
|
|
34008
|
+
const opts = command.optsWithGlobals();
|
|
34009
|
+
const cards = listHooks({
|
|
34010
|
+
dir: opts.catalog ? String(opts.catalog) : void 0,
|
|
34011
|
+
type: opts.type ? String(opts.type) : void 0,
|
|
34012
|
+
niche: opts.niche ? String(opts.niche) : void 0,
|
|
34013
|
+
status: opts.status ? String(opts.status) : void 0,
|
|
34014
|
+
formula: opts.formula ? String(opts.formula) : void 0
|
|
34015
|
+
});
|
|
34016
|
+
print(cards.map((c) => ({
|
|
34017
|
+
id: c.id,
|
|
34018
|
+
hook_type: c.hook_type,
|
|
34019
|
+
niche: c.niche,
|
|
34020
|
+
status: c.status,
|
|
34021
|
+
accounts: c.evidence.account_count,
|
|
34022
|
+
posts: c.evidence.post_count,
|
|
34023
|
+
formula_ids: c.formula_ids,
|
|
34024
|
+
title: c.title,
|
|
34025
|
+
format: c.format
|
|
34026
|
+
})), commandWantsJson(command));
|
|
34027
|
+
});
|
|
34028
|
+
jsonOption(hooks.command("get").requiredOption("--id <id>")).action(async (...args) => {
|
|
34029
|
+
const command = args.at(-1);
|
|
34030
|
+
const opts = command.optsWithGlobals();
|
|
34031
|
+
const card = await resolveHook(String(opts.id), { dir: opts.catalog ? String(opts.catalog) : void 0, baseUrl: gatewayBaseUrl() });
|
|
34032
|
+
print(card, commandWantsJson(command));
|
|
34033
|
+
});
|
|
34034
|
+
jsonOption(hooks.command("catalog")).action((...args) => {
|
|
34035
|
+
const command = args.at(-1);
|
|
34036
|
+
const opts = command.optsWithGlobals();
|
|
34037
|
+
print(loadHooksCatalog(opts.catalog ? String(opts.catalog) : void 0), commandWantsJson(command));
|
|
34038
|
+
});
|
|
34039
|
+
jsonOption(hooks.command("recommend").description("Rank hook molds for a topic/niche. Agent then fills format with the topic (do not copy example).").option("--topic <topic>", "User topic for {X}").option("--niche <niche>").option("--type <hook_type>").option("--limit <n>", "Max hooks", "5")).action((...args) => {
|
|
34040
|
+
const command = args.at(-1);
|
|
34041
|
+
const opts = command.optsWithGlobals();
|
|
34042
|
+
const result = recommendHooks({
|
|
34043
|
+
topic: opts.topic ? String(opts.topic) : void 0,
|
|
34044
|
+
niche: opts.niche ? String(opts.niche) : void 0,
|
|
34045
|
+
type: opts.type ? String(opts.type) : void 0,
|
|
34046
|
+
dir: opts.catalog ? String(opts.catalog) : void 0,
|
|
34047
|
+
limit: opts.limit != null ? Number(opts.limit) : 5
|
|
34048
|
+
});
|
|
34049
|
+
print(result, commandWantsJson(command));
|
|
34050
|
+
});
|
|
33572
34051
|
const mcp = program.command("mcp").description("Install and run the unified EasySociable MCP server");
|
|
33573
34052
|
jsonOption(mcp.command("install").requiredOption("--host <host>", "codex, claude, hermes, or openclaw").option("--config-path <path>")).action((...args) => {
|
|
33574
34053
|
const command = args.at(-1);
|
|
@@ -33598,5 +34077,5 @@ async function runV1Cli(argv = process.argv) {
|
|
|
33598
34077
|
|
|
33599
34078
|
// src/bin.ts
|
|
33600
34079
|
var exitCode = await runV1Cli();
|
|
33601
|
-
process.exit(exitCode);
|
|
34080
|
+
process.stdout.write("", () => process.exit(exitCode));
|
|
33602
34081
|
//# sourceMappingURL=easysociable.js.map
|