@creatorarmy/openclaw-creatorarmy 1.0.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/logger.ts ADDED
@@ -0,0 +1,54 @@
1
+ export type LoggerBackend = {
2
+ info(msg: string, ...args: unknown[]): void
3
+ warn(msg: string, ...args: unknown[]): void
4
+ error(msg: string, ...args: unknown[]): void
5
+ debug?(msg: string, ...args: unknown[]): void
6
+ }
7
+
8
+ const NOOP_LOGGER: LoggerBackend = {
9
+ info() {},
10
+ warn() {},
11
+ error() {},
12
+ debug() {},
13
+ }
14
+
15
+ let _backend: LoggerBackend = NOOP_LOGGER
16
+ let _debug = false
17
+
18
+ export function initLogger(backend: LoggerBackend, debug: boolean): void {
19
+ _backend = backend
20
+ _debug = debug
21
+ }
22
+
23
+ export const log = {
24
+ info(msg: string, ...args: unknown[]): void {
25
+ _backend.info(`creator-army: ${msg}`, ...args)
26
+ },
27
+
28
+ warn(msg: string, ...args: unknown[]): void {
29
+ _backend.warn(`creator-army: ${msg}`, ...args)
30
+ },
31
+
32
+ error(msg: string, err?: unknown): void {
33
+ const detail = err instanceof Error ? err.message : err ? String(err) : ""
34
+ _backend.error(`creator-army: ${msg}${detail ? ` — ${detail}` : ""}`)
35
+ },
36
+
37
+ debug(msg: string, ...args: unknown[]): void {
38
+ if (!_debug) return
39
+ const fn = _backend.debug ?? _backend.info
40
+ fn(`creator-army [debug]: ${msg}`, ...args)
41
+ },
42
+
43
+ debugRequest(method: string, params: Record<string, unknown>): void {
44
+ if (!_debug) return
45
+ const fn = _backend.debug ?? _backend.info
46
+ fn(`creator-army [debug] → ${method}`, JSON.stringify(params, null, 2))
47
+ },
48
+
49
+ debugResponse(method: string, data: unknown): void {
50
+ if (!_debug) return
51
+ const fn = _backend.debug ?? _backend.info
52
+ fn(`creator-army [debug] ← ${method}`, JSON.stringify(data, null, 2))
53
+ },
54
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "openclaw-creatorarmy",
3
+ "kind": "tool",
4
+ "uiHints": {
5
+ "apiKey": {
6
+ "label": "Creator Army API Key",
7
+ "sensitive": true,
8
+ "placeholder": "ca_...",
9
+ "help": "Your API key from Creator Army (or use ${CREATOR_ARMY_API_KEY})"
10
+ },
11
+ "baseUrl": {
12
+ "label": "API Base URL",
13
+ "placeholder": "https://api.creatorarmy.com",
14
+ "help": "Override the API base URL (or use ${CREATOR_ARMY_BASE_URL})",
15
+ "advanced": true
16
+ },
17
+ "debug": {
18
+ "label": "Debug Logging",
19
+ "help": "Enable verbose debug logs for API calls and responses",
20
+ "advanced": true
21
+ }
22
+ },
23
+ "configSchema": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "properties": {
27
+ "apiKey": { "type": "string" },
28
+ "baseUrl": { "type": "string" },
29
+ "debug": { "type": "boolean" }
30
+ },
31
+ "required": []
32
+ }
33
+ }
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@creatorarmy/openclaw-creatorarmy",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "OpenClaw Creator Army plugin",
6
+ "license": "MIT",
7
+ "dependencies": {
8
+ "@sinclair/typebox": "0.34.47"
9
+ },
10
+ "scripts": {
11
+ "check-types": "tsc --noEmit",
12
+ "lint": "bunx @biomejs/biome ci .",
13
+ "lint:fix": "bunx @biomejs/biome check --write ."
14
+ },
15
+ "peerDependencies": {
16
+ "openclaw": ">=2026.1.29"
17
+ },
18
+ "openclaw": {
19
+ "extensions": [
20
+ "./index.ts"
21
+ ]
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.9.3"
25
+ }
26
+ }
@@ -0,0 +1,110 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CreatorArmyClient } from "../../client.ts"
4
+ import type { CreatorArmyConfig } from "../../config.ts"
5
+ import { log } from "../../logger.ts"
6
+
7
+ export function registerBriefTools(
8
+ api: OpenClawPluginApi,
9
+ client: CreatorArmyClient,
10
+ _cfg: CreatorArmyConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "demand_engine_list_briefs",
15
+ label: "List Creative Briefs",
16
+ description:
17
+ "List creative briefs. Optionally filter by brand name. Briefs contain the selling sequence, hooks, building blocks, and creative type for an ad concept.",
18
+ parameters: Type.Object({
19
+ brand: Type.Optional(Type.String({ description: "Filter by brand name" })),
20
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
21
+ offset: Type.Optional(Type.Number({ description: "Pagination offset" })),
22
+ }),
23
+ async execute(
24
+ _id: string,
25
+ params: { brand?: string; limit?: number; offset?: number },
26
+ ) {
27
+ log.debug(`listing briefs: brand=${params.brand ?? "all"}`)
28
+ const data = await client.listBriefs(params)
29
+ if (data.briefs.length === 0) {
30
+ return {
31
+ content: [{ type: "text" as const, text: "No briefs found." }],
32
+ details: {},
33
+ }
34
+ }
35
+ const list = data.briefs
36
+ .map((b) => `- **${b.title}** (${b.brandName}) — ${b.creativeType ?? "untyped"}, ${b.customerType ?? "general"}`)
37
+ .join("\n")
38
+ return {
39
+ content: [{ type: "text" as const, text: `${data.total} brief(s):\n\n${list}` }],
40
+ details: { total: data.total },
41
+ }
42
+ },
43
+ },
44
+ { name: "demand_engine_list_briefs" },
45
+ )
46
+
47
+ api.registerTool(
48
+ {
49
+ name: "demand_engine_get_brief",
50
+ label: "Get Creative Brief",
51
+ description: "Get a single creative brief by ID with full selling sequence and hooks.",
52
+ parameters: Type.Object({
53
+ id: Type.String({ description: "Brief ID" }),
54
+ }),
55
+ async execute(_id: string, params: { id: string }) {
56
+ log.debug(`getting brief: ${params.id}`)
57
+ const brief = await client.getBrief(params.id)
58
+ if (!brief) {
59
+ return {
60
+ content: [{ type: "text" as const, text: "Brief not found." }],
61
+ details: {},
62
+ }
63
+ }
64
+ return {
65
+ content: [{ type: "text" as const, text: JSON.stringify(brief, null, 2) }],
66
+ details: { id: brief._id, title: brief.title },
67
+ }
68
+ },
69
+ },
70
+ { name: "demand_engine_get_brief" },
71
+ )
72
+
73
+ api.registerTool(
74
+ {
75
+ name: "demand_engine_save_brief",
76
+ label: "Save Creative Brief",
77
+ description:
78
+ "Save a new creative brief with selling sequence, hooks, building blocks, and creative type. This is Steps 2-3 of the demand engine.",
79
+ parameters: Type.Object({
80
+ brandName: Type.String({ description: "Brand name" }),
81
+ title: Type.String({ description: "Brief title" }),
82
+ customerType: Type.Optional(Type.String({ description: "Target customer type" })),
83
+ sellingSequence: Type.Optional(
84
+ Type.Object({
85
+ rapport: Type.Optional(Type.String()),
86
+ openLoop: Type.Optional(Type.String()),
87
+ valueStack: Type.Optional(Type.String()),
88
+ wowMoment: Type.Optional(Type.String()),
89
+ cta: Type.Optional(Type.String()),
90
+ }),
91
+ ),
92
+ hooks: Type.Optional(Type.Array(Type.String(), { description: "Hook options" })),
93
+ buildingBlocks: Type.Optional(Type.Array(Type.String(), { description: "Persuasion building blocks used" })),
94
+ creativeType: Type.Optional(Type.String({ description: "Ad format (e.g. founder-to-camera, ugc, mashup)" })),
95
+ platform: Type.Optional(Type.String({ description: "Target platform (e.g. meta, tiktok)" })),
96
+ duration: Type.Optional(Type.Number({ description: "Target duration in seconds" })),
97
+ notes: Type.Optional(Type.String({ description: "Production or strategy notes" })),
98
+ }),
99
+ async execute(_id: string, params: Record<string, unknown>) {
100
+ log.debug(`saving brief: ${params.title}`)
101
+ const brief = await client.saveBrief(params)
102
+ return {
103
+ content: [{ type: "text" as const, text: `Saved brief "${brief.title}" for ${brief.brandName}.` }],
104
+ details: { id: brief._id, title: brief.title },
105
+ }
106
+ },
107
+ },
108
+ { name: "demand_engine_save_brief" },
109
+ )
110
+ }
@@ -0,0 +1,93 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CreatorArmyClient } from "../../client.ts"
4
+ import type { CreatorArmyConfig } from "../../config.ts"
5
+ import { log } from "../../logger.ts"
6
+
7
+ export function registerContextTools(
8
+ api: OpenClawPluginApi,
9
+ client: CreatorArmyClient,
10
+ _cfg: CreatorArmyConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "demand_engine_list_brands",
15
+ label: "List Brands",
16
+ description: "List all brands with saved context in Creator Army.",
17
+ parameters: Type.Object({}),
18
+ async execute() {
19
+ log.debug("listing brands")
20
+ const brands = await client.listBrands()
21
+ if (brands.length === 0) {
22
+ return {
23
+ content: [{ type: "text" as const, text: "No brands found. Use demand_engine_save_context to create one." }],
24
+ details: {},
25
+ }
26
+ }
27
+ const list = brands.map((b) => `- ${b.brandName}${b.brandUrl ? ` (${b.brandUrl})` : ""}`).join("\n")
28
+ return {
29
+ content: [{ type: "text" as const, text: `Brands:\n\n${list}` }],
30
+ details: { count: brands.length },
31
+ }
32
+ },
33
+ },
34
+ { name: "demand_engine_list_brands" },
35
+ )
36
+
37
+ api.registerTool(
38
+ {
39
+ name: "demand_engine_get_context",
40
+ label: "Get Brand Context",
41
+ description:
42
+ "Get full brand context for a specific brand — personality, tone, visual style, ICP, customer types, barriers, motivators. This is Step 0 of the demand engine.",
43
+ parameters: Type.Object({
44
+ brand: Type.String({ description: "Brand name (case-insensitive)" }),
45
+ }),
46
+ async execute(_id: string, params: { brand: string }) {
47
+ log.debug(`getting context for: ${params.brand}`)
48
+ const ctx = await client.getBrandContext(params.brand)
49
+ if (!ctx) {
50
+ return {
51
+ content: [{ type: "text" as const, text: `No context found for "${params.brand}". Run customer excavation to build it.` }],
52
+ details: {},
53
+ }
54
+ }
55
+ return {
56
+ content: [{ type: "text" as const, text: JSON.stringify(ctx, null, 2) }],
57
+ details: { brandName: ctx.brandName },
58
+ }
59
+ },
60
+ },
61
+ { name: "demand_engine_get_context" },
62
+ )
63
+
64
+ api.registerTool(
65
+ {
66
+ name: "demand_engine_save_context",
67
+ label: "Save Brand Context",
68
+ description:
69
+ "Create or update brand context (upserts by brandName). Save personality, tone, visual style, ICP, customer types, barriers, and motivators.",
70
+ parameters: Type.Object({
71
+ brandName: Type.String({ description: "Brand name" }),
72
+ brandUrl: Type.Optional(Type.String({ description: "Brand website URL" })),
73
+ personality: Type.Optional(Type.String({ description: "Brand personality" })),
74
+ tone: Type.Optional(Type.String({ description: "Brand tone of voice" })),
75
+ visualStyle: Type.Optional(Type.String({ description: "Visual style for content" })),
76
+ icp: Type.Optional(Type.String({ description: "Ideal customer profile" })),
77
+ customerTypes: Type.Optional(Type.Array(Type.String(), { description: "Customer type names" })),
78
+ barriers: Type.Optional(Type.Array(Type.String(), { description: "Purchase barriers" })),
79
+ motivators: Type.Optional(Type.Array(Type.String(), { description: "Purchase motivators" })),
80
+ notes: Type.Optional(Type.String({ description: "Additional notes" })),
81
+ }),
82
+ async execute(_id: string, params: Record<string, unknown>) {
83
+ log.debug(`saving context for: ${params.brandName}`)
84
+ const ctx = await client.saveBrandContext(params)
85
+ return {
86
+ content: [{ type: "text" as const, text: `Saved brand context for "${ctx.brandName}".` }],
87
+ details: { brandName: ctx.brandName },
88
+ }
89
+ },
90
+ },
91
+ { name: "demand_engine_save_context" },
92
+ )
93
+ }
@@ -0,0 +1,74 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CreatorArmyClient } from "../../client.ts"
4
+ import type { CreatorArmyConfig } from "../../config.ts"
5
+ import { log } from "../../logger.ts"
6
+
7
+ export function registerExcavationTools(
8
+ api: OpenClawPluginApi,
9
+ client: CreatorArmyClient,
10
+ _cfg: CreatorArmyConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "demand_engine_get_excavation",
15
+ label: "Get Customer Excavation",
16
+ description:
17
+ "Get customer excavation data for a brand — customer types with their barriers and motivators, core problem, failed solutions, desired outcome, and unique mechanism. This is Step 1 of the demand engine.",
18
+ parameters: Type.Object({
19
+ brand: Type.String({ description: "Brand name" }),
20
+ }),
21
+ async execute(_id: string, params: { brand: string }) {
22
+ log.debug(`getting excavation for: ${params.brand}`)
23
+ const exc = await client.getExcavation(params.brand)
24
+ if (!exc) {
25
+ return {
26
+ content: [{ type: "text" as const, text: `No excavation found for "${params.brand}". Use demand_engine_save_excavation to create one.` }],
27
+ details: {},
28
+ }
29
+ }
30
+ return {
31
+ content: [{ type: "text" as const, text: JSON.stringify(exc, null, 2) }],
32
+ details: { brandName: exc.brandName },
33
+ }
34
+ },
35
+ },
36
+ { name: "demand_engine_get_excavation" },
37
+ )
38
+
39
+ api.registerTool(
40
+ {
41
+ name: "demand_engine_save_excavation",
42
+ label: "Save Customer Excavation",
43
+ description:
44
+ "Create or update customer excavation data (upserts by brandName). Save customer types with barriers/motivators, core problem, failed solutions, desired outcome, and unique mechanism.",
45
+ parameters: Type.Object({
46
+ brandName: Type.String({ description: "Brand name" }),
47
+ customerTypes: Type.Optional(
48
+ Type.Array(
49
+ Type.Object({
50
+ name: Type.String({ description: "Customer type name" }),
51
+ description: Type.Optional(Type.String({ description: "Description of this customer type" })),
52
+ barriers: Type.Optional(Type.Array(Type.String(), { description: "Their purchase barriers" })),
53
+ motivators: Type.Optional(Type.Array(Type.String(), { description: "What motivates them to buy" })),
54
+ }),
55
+ ),
56
+ ),
57
+ coreProblem: Type.Optional(Type.String({ description: "The core problem customers face" })),
58
+ failedSolutions: Type.Optional(Type.String({ description: "What they tried that didn't work" })),
59
+ desiredOutcome: Type.Optional(Type.String({ description: "What they want to achieve" })),
60
+ uniqueMechanism: Type.Optional(Type.String({ description: "Why this product works when others didn't" })),
61
+ notes: Type.Optional(Type.String({ description: "Additional notes" })),
62
+ }),
63
+ async execute(_id: string, params: Record<string, unknown>) {
64
+ log.debug(`saving excavation for: ${params.brandName}`)
65
+ const exc = await client.saveExcavation(params)
66
+ return {
67
+ content: [{ type: "text" as const, text: `Saved excavation for "${exc.brandName}".` }],
68
+ details: { brandName: exc.brandName },
69
+ }
70
+ },
71
+ },
72
+ { name: "demand_engine_save_excavation" },
73
+ )
74
+ }
@@ -0,0 +1,55 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CreatorArmyClient } from "../../client.ts"
4
+ import type { CreatorArmyConfig } from "../../config.ts"
5
+ import { log } from "../../logger.ts"
6
+
7
+ export function registerReferenceTools(
8
+ api: OpenClawPluginApi,
9
+ client: CreatorArmyClient,
10
+ _cfg: CreatorArmyConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "demand_engine_list_references",
15
+ label: "List Reference Docs",
16
+ description:
17
+ "List all available Creator Army demand engine reference documents (hook science, selling sequence, creative types, etc).",
18
+ parameters: Type.Object({}),
19
+ async execute() {
20
+ log.debug("listing references")
21
+ const refs = await client.listReferences()
22
+ const list = refs.map((r) => `- ${r.slug} (${r.filename})`).join("\n")
23
+ return {
24
+ content: [{ type: "text" as const, text: `Available references:\n\n${list}` }],
25
+ details: { count: refs.length },
26
+ }
27
+ },
28
+ },
29
+ { name: "demand_engine_list_references" },
30
+ )
31
+
32
+ api.registerTool(
33
+ {
34
+ name: "demand_engine_get_reference",
35
+ label: "Get Reference Doc",
36
+ description:
37
+ "Fetch a specific Creator Army demand engine reference document by slug. Use demand_engine_list_references to see available slugs. Contains frameworks for hooks, selling sequences, creative types, campaign architecture, and more.",
38
+ parameters: Type.Object({
39
+ slug: Type.String({
40
+ description:
41
+ "Reference slug (e.g. hook-science, selling-sequence, creative-types, customer-excavation, context-scan, building-blocks, lo-fi-production, campaign-architecture, learning-loop, case-studies)",
42
+ }),
43
+ }),
44
+ async execute(_id: string, params: { slug: string }) {
45
+ log.debug(`fetching reference: ${params.slug}`)
46
+ const ref = await client.getReference(params.slug)
47
+ return {
48
+ content: [{ type: "text" as const, text: ref.content ?? "No content found." }],
49
+ details: { slug: ref.slug, filename: ref.filename },
50
+ }
51
+ },
52
+ },
53
+ { name: "demand_engine_get_reference" },
54
+ )
55
+ }
@@ -0,0 +1,124 @@
1
+ import { Type } from "@sinclair/typebox"
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk"
3
+ import type { CreatorArmyClient } from "../../client.ts"
4
+ import type { CreatorArmyConfig } from "../../config.ts"
5
+ import { log } from "../../logger.ts"
6
+
7
+ export function registerScriptTools(
8
+ api: OpenClawPluginApi,
9
+ client: CreatorArmyClient,
10
+ _cfg: CreatorArmyConfig,
11
+ ): void {
12
+ api.registerTool(
13
+ {
14
+ name: "demand_engine_list_scripts",
15
+ label: "List Scripts",
16
+ description:
17
+ "List generated ad scripts. Optionally filter by brand or brief ID.",
18
+ parameters: Type.Object({
19
+ brand: Type.Optional(Type.String({ description: "Filter by brand name" })),
20
+ briefId: Type.Optional(Type.String({ description: "Filter by parent brief ID" })),
21
+ limit: Type.Optional(Type.Number({ description: "Max results (default 20)" })),
22
+ offset: Type.Optional(Type.Number({ description: "Pagination offset" })),
23
+ }),
24
+ async execute(
25
+ _id: string,
26
+ params: { brand?: string; briefId?: string; limit?: number; offset?: number },
27
+ ) {
28
+ log.debug(`listing scripts: brand=${params.brand ?? "all"}`)
29
+ const data = await client.listScripts(params)
30
+ if (data.scripts.length === 0) {
31
+ return {
32
+ content: [{ type: "text" as const, text: "No scripts found." }],
33
+ details: {},
34
+ }
35
+ }
36
+ const list = data.scripts
37
+ .map((s) => `- **${s.title}** (${s.brandName}) — ${s.format ?? "untyped"}, ${s.duration ?? "?"}s`)
38
+ .join("\n")
39
+ return {
40
+ content: [{ type: "text" as const, text: `${data.total} script(s):\n\n${list}` }],
41
+ details: { total: data.total },
42
+ }
43
+ },
44
+ },
45
+ { name: "demand_engine_list_scripts" },
46
+ )
47
+
48
+ api.registerTool(
49
+ {
50
+ name: "demand_engine_get_script",
51
+ label: "Get Script",
52
+ description: "Get a single ad script by ID with full body, selling sequence, and persuasion checklist.",
53
+ parameters: Type.Object({
54
+ id: Type.String({ description: "Script ID" }),
55
+ }),
56
+ async execute(_id: string, params: { id: string }) {
57
+ log.debug(`getting script: ${params.id}`)
58
+ const script = await client.getScript(params.id)
59
+ if (!script) {
60
+ return {
61
+ content: [{ type: "text" as const, text: "Script not found." }],
62
+ details: {},
63
+ }
64
+ }
65
+ return {
66
+ content: [{ type: "text" as const, text: JSON.stringify(script, null, 2) }],
67
+ details: { id: script._id, title: script.title },
68
+ }
69
+ },
70
+ },
71
+ { name: "demand_engine_get_script" },
72
+ )
73
+
74
+ api.registerTool(
75
+ {
76
+ name: "demand_engine_save_script",
77
+ label: "Save Script",
78
+ description:
79
+ "Save a generated ad script with hook, open loop, body, CTA, selling sequence, building blocks, and persuasion checklist. This is Steps 3-4 of the demand engine.",
80
+ parameters: Type.Object({
81
+ brandName: Type.String({ description: "Brand name" }),
82
+ title: Type.String({ description: "Script title" }),
83
+ briefId: Type.Optional(Type.String({ description: "Parent brief ID" })),
84
+ customerType: Type.Optional(Type.String({ description: "Target customer type" })),
85
+ format: Type.Optional(Type.String({ description: "Ad format (e.g. founder-to-camera, ugc)" })),
86
+ platform: Type.Optional(Type.String({ description: "Target platform" })),
87
+ duration: Type.Optional(Type.Number({ description: "Duration in seconds" })),
88
+ hook: Type.Optional(Type.String({ description: "The hook (first 3 seconds)" })),
89
+ openLoop: Type.Optional(Type.String({ description: "The open loop (seconds 3-10)" })),
90
+ body: Type.Optional(Type.String({ description: "Full script body with timestamps and directions" })),
91
+ cta: Type.Optional(Type.String({ description: "Call to action" })),
92
+ sellingSequence: Type.Optional(
93
+ Type.Object({
94
+ rapport: Type.Optional(Type.String()),
95
+ openLoop: Type.Optional(Type.String()),
96
+ valueStack: Type.Optional(Type.String()),
97
+ wowMoment: Type.Optional(Type.String()),
98
+ cta: Type.Optional(Type.String()),
99
+ }),
100
+ ),
101
+ buildingBlocks: Type.Optional(Type.Array(Type.String(), { description: "Persuasion building blocks used" })),
102
+ persuasionChecklist: Type.Optional(
103
+ Type.Object({
104
+ ethos: Type.Optional(Type.Boolean({ description: "Credibility/trust signal present" })),
105
+ logos: Type.Optional(Type.Boolean({ description: "Data or demonstration present" })),
106
+ pathos: Type.Optional(Type.Boolean({ description: "Makes viewer feel something" })),
107
+ metaphor: Type.Optional(Type.Boolean({ description: "Concept is tangible/clear" })),
108
+ brevity: Type.Optional(Type.Boolean({ description: "Can cut 30% and lose nothing" })),
109
+ }),
110
+ ),
111
+ notes: Type.Optional(Type.String({ description: "Production notes" })),
112
+ }),
113
+ async execute(_id: string, params: Record<string, unknown>) {
114
+ log.debug(`saving script: ${params.title}`)
115
+ const script = await client.saveScript(params)
116
+ return {
117
+ content: [{ type: "text" as const, text: `Saved script "${script.title}" for ${script.brandName}.` }],
118
+ details: { id: script._id, title: script.title },
119
+ }
120
+ },
121
+ },
122
+ { name: "demand_engine_save_script" },
123
+ )
124
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "allowImportingTsExtensions": true,
12
+ "noEmit": true,
13
+ "rootDir": "."
14
+ },
15
+ "include": [
16
+ "*.ts",
17
+ "tools/**/*.ts",
18
+ "hooks/*.ts",
19
+ "commands/*.ts"
20
+ ],
21
+ "exclude": ["node_modules", "dist"]
22
+ }