@rcs-campaigns/agent-toolkit 0.1.8 → 0.1.10
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 +9 -8
- package/dist/cli.js +41 -32
- package/dist/cliDispatch.d.ts +65 -0
- package/dist/cliDispatch.d.ts.map +1 -0
- package/dist/cliDispatch.js +260 -0
- package/dist/client.d.ts +21 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +51 -10
- package/dist/mcp.js +5 -4
- package/dist/tools.js +13 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,14 +24,18 @@ key** (Admin only). Pick the scopes the agent needs, then **copy the key — it
|
|
|
24
24
|
shown exactly once** (`rcsk_live_…`). Treat it like a password; you can
|
|
25
25
|
revoke/rotate it from the same screen.
|
|
26
26
|
|
|
27
|
-
Scopes: `
|
|
27
|
+
Scopes: `campaigns:read`, `campaigns:write`, `campaigns:submit`,
|
|
28
28
|
`marketplace:clone`, `messages:send`, `usage:read`, `surveys:read`,
|
|
29
29
|
`surveys:write`, `surveys:submit`, `surveys:send`, `results:read`.
|
|
30
30
|
|
|
31
|
-
### 2.
|
|
31
|
+
### 2. Gateway URL (optional)
|
|
32
|
+
|
|
33
|
+
The toolkit talks to the hosted production gateway by default, so a production
|
|
34
|
+
key needs nothing else. Set `RCS_GATEWAY_URL` (or pass `--url`) only to target
|
|
35
|
+
dev or a local gateway:
|
|
32
36
|
|
|
33
37
|
```bash
|
|
34
|
-
RCS_GATEWAY_URL=
|
|
38
|
+
RCS_GATEWAY_URL=http://localhost:3002
|
|
35
39
|
```
|
|
36
40
|
|
|
37
41
|
> The key is tied to the environment it was minted in — a key from one
|
|
@@ -67,7 +71,6 @@ pnpm --filter @rcs-campaigns/agent-toolkit build # -> dist/mcp.js, dist/cli.js
|
|
|
67
71
|
### 4. Verify the key works (before wiring up a host)
|
|
68
72
|
|
|
69
73
|
```bash
|
|
70
|
-
export RCS_GATEWAY_URL=https://rcs-templates-api-dot-msg2ai-server.ue.r.appspot.com
|
|
71
74
|
export RCS_AGENT_KEY=rcsk_live_…
|
|
72
75
|
npx -y -p @rcs-campaigns/agent-toolkit rcs capabilities
|
|
73
76
|
```
|
|
@@ -88,7 +91,6 @@ name.
|
|
|
88
91
|
|
|
89
92
|
```bash
|
|
90
93
|
claude mcp add rcs-campaigns \
|
|
91
|
-
--env RCS_GATEWAY_URL=https://rcs-templates-api-dot-msg2ai-server.ue.r.appspot.com \
|
|
92
94
|
--env RCS_AGENT_KEY=rcsk_live_… \
|
|
93
95
|
-- npx -y -p @rcs-campaigns/agent-toolkit rcs-mcp
|
|
94
96
|
```
|
|
@@ -110,7 +112,6 @@ Edit `claude_desktop_config.json`
|
|
|
110
112
|
"command": "npx",
|
|
111
113
|
"args": ["-y", "-p", "@rcs-campaigns/agent-toolkit", "rcs-mcp"],
|
|
112
114
|
"env": {
|
|
113
|
-
"RCS_GATEWAY_URL": "https://rcs-templates-api-dot-msg2ai-server.ue.r.appspot.com",
|
|
114
115
|
"RCS_AGENT_KEY": "rcsk_live_…"
|
|
115
116
|
}
|
|
116
117
|
}
|
|
@@ -160,7 +161,7 @@ out — pipe into `jq`, wire into CI, or call from a skill.
|
|
|
160
161
|
| Symptom | Cause / fix |
|
|
161
162
|
|---|---|
|
|
162
163
|
| `No agent key configured` | `RCS_AGENT_KEY` not reaching the process — for MCP hosts it must be in the server's `env` block, not your shell. |
|
|
163
|
-
| `Gateway 401` | Key wrong, revoked, or minted in a different environment than `RCS_GATEWAY_URL`
|
|
164
|
+
| `Gateway 401` | Key wrong, revoked, or minted in a different environment than the gateway you're calling (production unless `RCS_GATEWAY_URL` says otherwise). |
|
|
164
165
|
| `Gateway 403` (scope) | The key lacks the tool's scope — mint a new key with it (scopes are fixed at creation). |
|
|
165
166
|
| Host shows no tools | Wrong path to `dist/mcp.js`, or the package wasn't built (`pnpm --filter @rcs-campaigns/agent-toolkit build`). |
|
|
166
167
|
| Empty search results | The org has no published/owned templates yet — clone one from the marketplace first. |
|
|
@@ -172,7 +173,7 @@ out — pipe into `jq`, wire into CI, or call from a skill.
|
|
|
172
173
|
```ts
|
|
173
174
|
import { GatewayClient, TOOLS } from '@rcs-campaigns/agent-toolkit';
|
|
174
175
|
|
|
175
|
-
const client = GatewayClient.fromEnv(); // reads
|
|
176
|
+
const client = GatewayClient.fromEnv(); // reads RCS_AGENT_KEY (+ optional RCS_GATEWAY_URL)
|
|
176
177
|
const { items } = await client.call({
|
|
177
178
|
method: 'GET',
|
|
178
179
|
path: '/templates',
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
* org's own brand) and reads RCS_*. Either prefix is accepted under either bin.
|
|
13
13
|
*
|
|
14
14
|
* Config (flags override env):
|
|
15
|
-
* --url <gateway base url> | WA_GATEWAY_URL / RCS_GATEWAY_URL
|
|
15
|
+
* --url <gateway base url> | WA_GATEWAY_URL / RCS_GATEWAY_URL (optional;
|
|
16
|
+
* defaults to the hosted production gateway)
|
|
16
17
|
* --key <rcsk_…> | WA_AGENT_KEY / RCS_AGENT_KEY
|
|
17
18
|
* --channel RCS|WHATSAPP | WA_DEFAULT_CHANNEL / RCS_DEFAULT_CHANNEL
|
|
18
19
|
*
|
|
@@ -29,30 +30,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
30
|
*/
|
|
30
31
|
const node_fs_1 = require("node:fs");
|
|
31
32
|
const client_1 = require("./client");
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const out = { _: [], flags: {}, multi: { to: [], var: [], audience: [] } };
|
|
35
|
-
for (let i = 0; i < argv.length; i++) {
|
|
36
|
-
const a = argv[i];
|
|
37
|
-
if (a.startsWith('--')) {
|
|
38
|
-
const key = a.slice(2);
|
|
39
|
-
const next = argv[i + 1];
|
|
40
|
-
const takesValue = next !== undefined && !next.startsWith('--');
|
|
41
|
-
const value = takesValue ? argv[++i] : true;
|
|
42
|
-
if (key === 'to' || key === 'var' || key === 'audience') {
|
|
43
|
-
if (typeof value === 'string')
|
|
44
|
-
out.multi[key].push(value);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
out.flags[key] = value;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
out._.push(a);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
33
|
+
const tools_1 = require("./tools");
|
|
34
|
+
const cliDispatch_1 = require("./cliDispatch");
|
|
56
35
|
/**
|
|
57
36
|
* Read a JSON body from a file, letting the skin's default channel apply when
|
|
58
37
|
* the document doesn't name one. An explicit `channel` in the file always wins,
|
|
@@ -83,11 +62,28 @@ function kvPairs(pairs) {
|
|
|
83
62
|
function print(data) {
|
|
84
63
|
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
85
64
|
}
|
|
65
|
+
// Catalog dispatch: everything without a curated command below is reached
|
|
66
|
+
// through the tool catalog, so a new gateway capability is callable from the
|
|
67
|
+
// CLI as soon as it has a `tools.ts` entry. The parsing lives in
|
|
68
|
+
// ./cliDispatch so it can be tested without running this entry point.
|
|
69
|
+
async function dispatchCatalog(words, argv, client, defaultChannel) {
|
|
70
|
+
const match = (0, cliDispatch_1.resolveTool)(words);
|
|
71
|
+
if (!match) {
|
|
72
|
+
fail(`Unknown command: ${words.join(' ') || '(none)'}\n\n${USAGE}`);
|
|
73
|
+
}
|
|
74
|
+
const { tool, used } = match;
|
|
75
|
+
const args = (0, cliDispatch_1.argsFromArgv)(tool, words.slice(used), argv);
|
|
76
|
+
return print(await client.call(tool.toRequest((0, tools_1.withDefaultChannel)(tool, args, defaultChannel))));
|
|
77
|
+
}
|
|
86
78
|
const SKIN = (0, client_1.detectSkin)();
|
|
87
|
-
const
|
|
79
|
+
const BRAND = (0, client_1.detectBrand)();
|
|
80
|
+
/** The bin name as the user typed it; the surveys packages ship their own. */
|
|
81
|
+
const BIN = BRAND === 'wa-surveys' || BRAND === 'rcs-surveys' ? BRAND : SKIN === 'wa' ? 'wa' : 'rcs';
|
|
82
|
+
/** Env var prefix: WA_* or RCS_*, never the bin name (wa-surveys → WA_*). */
|
|
83
|
+
const ENV_PREFIX = SKIN === 'wa' ? 'WA' : 'RCS';
|
|
88
84
|
/** Show the channel this skin actually defaults to in its own help text. */
|
|
89
85
|
const CHANNEL_EG = SKIN === 'wa' ? 'WHATSAPP' : 'RCS';
|
|
90
|
-
const USAGE = `${BIN} — ${(0, client_1.skinLabels)(
|
|
86
|
+
const USAGE = `${BIN} — ${(0, client_1.skinLabels)(BRAND).product} agent CLI v${(0, client_1.readVersion)()}
|
|
91
87
|
|
|
92
88
|
Usage:
|
|
93
89
|
${BIN} capabilities
|
|
@@ -113,9 +109,18 @@ Surveys:
|
|
|
113
109
|
${BIN} survey sends <id>
|
|
114
110
|
${BIN} survey results <id>
|
|
115
111
|
|
|
116
|
-
|
|
112
|
+
Everything else, straight from the tool catalog (scope shown after each):
|
|
113
|
+
${(0, cliDispatch_1.catalogUsage)(BIN)}
|
|
114
|
+
|
|
115
|
+
Any catalog tool name also works as typed (${BIN} list_conversations, or
|
|
116
|
+
${BIN} list-conversations). Structured arguments come from --file <doc.json>:
|
|
117
|
+
an object merges at the top level, a bare array fills the tool's list argument.
|
|
118
|
+
Repeat a flag or comma-separate it for lists (--event a --event b).
|
|
119
|
+
|
|
120
|
+
Config: --key/${ENV_PREFIX}_AGENT_KEY (required),
|
|
121
|
+
--url/${ENV_PREFIX}_GATEWAY_URL (optional; defaults to the hosted gateway)`;
|
|
117
122
|
async function run() {
|
|
118
|
-
const argv = parseArgs(process.argv.slice(2));
|
|
123
|
+
const argv = (0, cliDispatch_1.parseArgs)(process.argv.slice(2));
|
|
119
124
|
const cmd = argv._[0];
|
|
120
125
|
if (!cmd || cmd === 'help' || argv.flags.help) {
|
|
121
126
|
process.stdout.write(`${USAGE}\n`);
|
|
@@ -340,12 +345,12 @@ async function run() {
|
|
|
340
345
|
path: `/surveys/${enc(id)}/results`
|
|
341
346
|
}));
|
|
342
347
|
default:
|
|
343
|
-
|
|
348
|
+
// `survey render|clone|delete` and anything the catalog grows later.
|
|
349
|
+
return dispatchCatalog(argv._, argv, client, config.defaultChannel);
|
|
344
350
|
}
|
|
345
|
-
return;
|
|
346
351
|
}
|
|
347
352
|
default:
|
|
348
|
-
|
|
353
|
+
return dispatchCatalog(argv._, argv, client, config.defaultChannel);
|
|
349
354
|
}
|
|
350
355
|
}
|
|
351
356
|
function str(v) {
|
|
@@ -363,6 +368,10 @@ function fail(message) {
|
|
|
363
368
|
process.exit(2);
|
|
364
369
|
}
|
|
365
370
|
run().catch((err) => {
|
|
371
|
+
if (err instanceof cliDispatch_1.UsageError) {
|
|
372
|
+
// Bad arguments, not a failed call — same exit 2 the curated commands use.
|
|
373
|
+
fail(err.message);
|
|
374
|
+
}
|
|
366
375
|
if (err instanceof client_1.GatewayError) {
|
|
367
376
|
process.stderr.write(`Gateway ${err.status}: ${err.message}\n`);
|
|
368
377
|
if (err.details)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type ToolDef } from './tools';
|
|
2
|
+
export interface ParsedArgs {
|
|
3
|
+
_: string[];
|
|
4
|
+
flags: Record<string, string | boolean>;
|
|
5
|
+
multi: Record<string, string[]>;
|
|
6
|
+
/**
|
|
7
|
+
* EVERY flag's values in order, including the ones `multi` claims and the
|
|
8
|
+
* ones `flags` collapses to their last occurrence. Catalog dispatch needs
|
|
9
|
+
* this: an array-typed argument (`--event a --event b`) cannot be expressed
|
|
10
|
+
* by either of the other two, and hardcoding another name into `multi` would
|
|
11
|
+
* put us back to editing this file whenever the gateway grows an argument.
|
|
12
|
+
*/
|
|
13
|
+
repeats: Record<string, string[]>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A caller error — bad or missing arguments, unknown command.
|
|
17
|
+
*
|
|
18
|
+
* Thrown rather than exiting so these functions stay testable; `cli.ts` turns
|
|
19
|
+
* it into the same stderr + exit 2 every usage error has always produced.
|
|
20
|
+
*/
|
|
21
|
+
export declare class UsageError extends Error {
|
|
22
|
+
}
|
|
23
|
+
/** Minimal argv parser: --flag value, --bool, and repeatable --to/--var. */
|
|
24
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
25
|
+
/**
|
|
26
|
+
* Catalog tools that already have a curated command, listed only so generated
|
|
27
|
+
* help does not print a second, uglier spelling of a command that has a good
|
|
28
|
+
* one. Being in this set does not disable catalog dispatch for the tool.
|
|
29
|
+
*/
|
|
30
|
+
export declare const CURATED: Set<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Friendlier spellings for generically-dispatched tools: `wa audience get`
|
|
33
|
+
* rather than `wa get_audience`. The catalog name always works too, in snake or
|
|
34
|
+
* kebab case, so a tool added tomorrow is callable before anyone writes it an
|
|
35
|
+
* alias here.
|
|
36
|
+
*/
|
|
37
|
+
export declare const ALIASES: Record<string, string>;
|
|
38
|
+
/** The `:param` names in a tool's route, in the order the route names them. */
|
|
39
|
+
export declare function pathParams(tool: ToolDef): string[];
|
|
40
|
+
/** The friendly spelling of a tool, for help text and error messages. */
|
|
41
|
+
export declare function cliName(tool: ToolDef): string;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve argv's leading words to a catalog tool. Two words are tried first so
|
|
44
|
+
* `audience get` wins over a tool literally named `audience`; the number of
|
|
45
|
+
* words consumed comes back so the caller knows where positionals start.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveTool(words: string[]): {
|
|
48
|
+
tool: ToolDef;
|
|
49
|
+
used: number;
|
|
50
|
+
} | undefined;
|
|
51
|
+
/** Coerce raw flag strings to the JSON-Schema type the tool declares. */
|
|
52
|
+
export declare function coerce(schema: any, values: string[]): unknown;
|
|
53
|
+
/**
|
|
54
|
+
* Build a tool's arguments from argv: positionals fill the route's `:params`,
|
|
55
|
+
* flags fill everything else, and `--file` supplies the structured ones — an
|
|
56
|
+
* object merges at the top level, a bare array fills the tool's single
|
|
57
|
+
* array-typed argument, so `--file contacts.json` works whether the file holds
|
|
58
|
+
* `[…]` or `{"contacts":[…]}`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function argsFromArgv(tool: ToolDef, positionals: string[], argv: ParsedArgs, readFile?: (path: string) => string): Record<string, any>;
|
|
61
|
+
/** One tool's usage line, derived from its schema rather than written by hand. */
|
|
62
|
+
export declare function toolUsage(tool: ToolDef, bin?: string): string;
|
|
63
|
+
/** The generated half of the help: every catalog tool without a curated command. */
|
|
64
|
+
export declare function catalogUsage(bin?: string): string;
|
|
65
|
+
//# sourceMappingURL=cliDispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cliDispatch.d.ts","sourceRoot":"","sources":["../src/cliDispatch.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,EAAE,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAChC;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACnC;AAED;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAExC,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyBpD;AAED;;;;GAIG;AACH,eAAO,MAAM,OAAO,aAyBlB,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwB1C,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,EAAE,CAElD;AAED,yEAAyE;AACzE,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAG7C;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EAAE,GACd;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAa7C;AAED,yEAAyE;AACzE,wBAAgB,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CA4B7D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,MAAM,EAAE,EACrB,IAAI,EAAE,UAAU,EAChB,QAAQ,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAuC,GAClE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAiDrB;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,SAAQ,GAAG,MAAM,CAc5D;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,GAAG,SAAQ,GAAG,MAAM,CAIhD"}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALIASES = exports.CURATED = exports.UsageError = void 0;
|
|
4
|
+
exports.parseArgs = parseArgs;
|
|
5
|
+
exports.pathParams = pathParams;
|
|
6
|
+
exports.cliName = cliName;
|
|
7
|
+
exports.resolveTool = resolveTool;
|
|
8
|
+
exports.coerce = coerce;
|
|
9
|
+
exports.argsFromArgv = argsFromArgv;
|
|
10
|
+
exports.toolUsage = toolUsage;
|
|
11
|
+
exports.catalogUsage = catalogUsage;
|
|
12
|
+
/**
|
|
13
|
+
* Argument parsing and catalog dispatch for the `rcs` / `wa` CLI.
|
|
14
|
+
*
|
|
15
|
+
* Split out of `cli.ts` because that file is an entry point: it calls `run()`
|
|
16
|
+
* at module scope, so importing it to test anything would run the CLI. These
|
|
17
|
+
* are the pure parts — argv in, tool arguments out — and they are where the
|
|
18
|
+
* behaviour worth testing lives.
|
|
19
|
+
*
|
|
20
|
+
* WHY CATALOG DISPATCH EXISTS: `tools.ts` already declares every gateway
|
|
21
|
+
* capability (route, schema, scope) and a CI test fails when a route has no
|
|
22
|
+
* entry. The CLI used to hand-write its own routes anyway, so it drifted to
|
|
23
|
+
* ~20 of the catalog's 48 tools while the MCP server had all of them. Now the
|
|
24
|
+
* curated commands stay hand-written — they earn it, folding several flags into
|
|
25
|
+
* one endpoint — and everything else is reached through the catalog, so a new
|
|
26
|
+
* capability is callable from the CLI the moment it has a catalog entry.
|
|
27
|
+
*/
|
|
28
|
+
const node_fs_1 = require("node:fs");
|
|
29
|
+
const tools_1 = require("./tools");
|
|
30
|
+
/**
|
|
31
|
+
* A caller error — bad or missing arguments, unknown command.
|
|
32
|
+
*
|
|
33
|
+
* Thrown rather than exiting so these functions stay testable; `cli.ts` turns
|
|
34
|
+
* it into the same stderr + exit 2 every usage error has always produced.
|
|
35
|
+
*/
|
|
36
|
+
class UsageError extends Error {
|
|
37
|
+
}
|
|
38
|
+
exports.UsageError = UsageError;
|
|
39
|
+
/** Minimal argv parser: --flag value, --bool, and repeatable --to/--var. */
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
const out = {
|
|
42
|
+
_: [],
|
|
43
|
+
flags: {},
|
|
44
|
+
multi: { to: [], var: [], audience: [] },
|
|
45
|
+
repeats: {}
|
|
46
|
+
};
|
|
47
|
+
for (let i = 0; i < argv.length; i++) {
|
|
48
|
+
const a = argv[i];
|
|
49
|
+
if (a.startsWith('--')) {
|
|
50
|
+
const key = a.slice(2);
|
|
51
|
+
const next = argv[i + 1];
|
|
52
|
+
const takesValue = next !== undefined && !next.startsWith('--');
|
|
53
|
+
const value = takesValue ? argv[++i] : true;
|
|
54
|
+
if (typeof value === 'string')
|
|
55
|
+
(out.repeats[key] ??= []).push(value);
|
|
56
|
+
if (key === 'to' || key === 'var' || key === 'audience') {
|
|
57
|
+
if (typeof value === 'string')
|
|
58
|
+
out.multi[key].push(value);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
out.flags[key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
out._.push(a);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Catalog tools that already have a curated command, listed only so generated
|
|
72
|
+
* help does not print a second, uglier spelling of a command that has a good
|
|
73
|
+
* one. Being in this set does not disable catalog dispatch for the tool.
|
|
74
|
+
*/
|
|
75
|
+
exports.CURATED = new Set([
|
|
76
|
+
'capabilities',
|
|
77
|
+
'search_templates',
|
|
78
|
+
'get_template',
|
|
79
|
+
'render_template',
|
|
80
|
+
'create_template',
|
|
81
|
+
'clone_template',
|
|
82
|
+
'submit_template',
|
|
83
|
+
'send_message',
|
|
84
|
+
'upload_media',
|
|
85
|
+
'get_knowledge',
|
|
86
|
+
'set_knowledge',
|
|
87
|
+
'delete_knowledge',
|
|
88
|
+
'enable_knowledge',
|
|
89
|
+
'disable_knowledge',
|
|
90
|
+
'get_usage',
|
|
91
|
+
'search_surveys',
|
|
92
|
+
'get_survey',
|
|
93
|
+
'create_survey',
|
|
94
|
+
'update_survey',
|
|
95
|
+
'submit_survey',
|
|
96
|
+
'send_survey',
|
|
97
|
+
'send_survey_test',
|
|
98
|
+
'list_survey_sends',
|
|
99
|
+
'get_results'
|
|
100
|
+
]);
|
|
101
|
+
/**
|
|
102
|
+
* Friendlier spellings for generically-dispatched tools: `wa audience get`
|
|
103
|
+
* rather than `wa get_audience`. The catalog name always works too, in snake or
|
|
104
|
+
* kebab case, so a tool added tomorrow is callable before anyone writes it an
|
|
105
|
+
* alias here.
|
|
106
|
+
*/
|
|
107
|
+
exports.ALIASES = {
|
|
108
|
+
audiences: 'search_audiences',
|
|
109
|
+
'audience get': 'get_audience',
|
|
110
|
+
'audience contacts': 'list_audience_contacts',
|
|
111
|
+
'audience create': 'create_audience',
|
|
112
|
+
'audience import': 'import_contacts',
|
|
113
|
+
'audience delete': 'delete_audience',
|
|
114
|
+
contacts: 'list_contacts',
|
|
115
|
+
'contact get': 'get_contact',
|
|
116
|
+
'contact update': 'update_contact',
|
|
117
|
+
conversations: 'list_conversations',
|
|
118
|
+
'conversation get': 'get_conversation',
|
|
119
|
+
'conversation assign': 'assign_conversation',
|
|
120
|
+
webhooks: 'list_webhooks',
|
|
121
|
+
'webhook create': 'create_webhook',
|
|
122
|
+
'webhook update': 'update_webhook',
|
|
123
|
+
'webhook delete': 'delete_webhook',
|
|
124
|
+
'webhook test': 'test_webhook',
|
|
125
|
+
'template update': 'update_template',
|
|
126
|
+
'template duplicate': 'duplicate_template',
|
|
127
|
+
'template delete': 'delete_template',
|
|
128
|
+
'survey render': 'render_survey',
|
|
129
|
+
'survey clone': 'clone_survey',
|
|
130
|
+
'survey delete': 'delete_survey'
|
|
131
|
+
};
|
|
132
|
+
/** The `:param` names in a tool's route, in the order the route names them. */
|
|
133
|
+
function pathParams(tool) {
|
|
134
|
+
return (tool.path.match(/:([A-Za-z0-9_]+)/g) ?? []).map((s) => s.slice(1));
|
|
135
|
+
}
|
|
136
|
+
/** The friendly spelling of a tool, for help text and error messages. */
|
|
137
|
+
function cliName(tool) {
|
|
138
|
+
const alias = Object.entries(exports.ALIASES).find(([, name]) => name === tool.name);
|
|
139
|
+
return alias ? alias[0] : tool.name;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve argv's leading words to a catalog tool. Two words are tried first so
|
|
143
|
+
* `audience get` wins over a tool literally named `audience`; the number of
|
|
144
|
+
* words consumed comes back so the caller knows where positionals start.
|
|
145
|
+
*/
|
|
146
|
+
function resolveTool(words) {
|
|
147
|
+
const two = words.slice(0, 2).join(' ');
|
|
148
|
+
if (words.length >= 2 && exports.ALIASES[two]) {
|
|
149
|
+
const tool = (0, tools_1.findTool)(exports.ALIASES[two]);
|
|
150
|
+
if (tool)
|
|
151
|
+
return { tool, used: 2 };
|
|
152
|
+
}
|
|
153
|
+
const one = words[0];
|
|
154
|
+
if (!one)
|
|
155
|
+
return undefined;
|
|
156
|
+
const direct = (exports.ALIASES[one] ? (0, tools_1.findTool)(exports.ALIASES[one]) : undefined) ??
|
|
157
|
+
(0, tools_1.findTool)(one) ??
|
|
158
|
+
(0, tools_1.findTool)(one.replace(/-/g, '_'));
|
|
159
|
+
return direct ? { tool: direct, used: 1 } : undefined;
|
|
160
|
+
}
|
|
161
|
+
/** Coerce raw flag strings to the JSON-Schema type the tool declares. */
|
|
162
|
+
function coerce(schema, values) {
|
|
163
|
+
const raw = schema?.type;
|
|
164
|
+
const type = Array.isArray(raw) ? raw.find((t) => t !== 'null') : raw;
|
|
165
|
+
if (type === 'array') {
|
|
166
|
+
// Repeat the flag, or comma-separate it — both read naturally in a shell.
|
|
167
|
+
const items = values.length > 1 ? values : values[0].split(',');
|
|
168
|
+
const itemType = schema.items?.type;
|
|
169
|
+
return items.map((v) => itemType === 'number' || itemType === 'integer'
|
|
170
|
+
? Number(v)
|
|
171
|
+
: itemType === 'boolean'
|
|
172
|
+
? v !== 'false'
|
|
173
|
+
: v);
|
|
174
|
+
}
|
|
175
|
+
const last = values[values.length - 1];
|
|
176
|
+
if (type === 'number' || type === 'integer')
|
|
177
|
+
return Number(last);
|
|
178
|
+
if (type === 'boolean')
|
|
179
|
+
return last !== 'false';
|
|
180
|
+
if (type === 'object') {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(last);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
throw new UsageError('an object argument must be JSON on the command line, or come from --file');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return last;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Build a tool's arguments from argv: positionals fill the route's `:params`,
|
|
192
|
+
* flags fill everything else, and `--file` supplies the structured ones — an
|
|
193
|
+
* object merges at the top level, a bare array fills the tool's single
|
|
194
|
+
* array-typed argument, so `--file contacts.json` works whether the file holds
|
|
195
|
+
* `[…]` or `{"contacts":[…]}`.
|
|
196
|
+
*/
|
|
197
|
+
function argsFromArgv(tool, positionals, argv, readFile = (p) => (0, node_fs_1.readFileSync)(p, 'utf8')) {
|
|
198
|
+
const props = (tool.inputSchema.properties ?? {});
|
|
199
|
+
const args = {};
|
|
200
|
+
const file = typeof argv.flags.file === 'string' ? argv.flags.file : undefined;
|
|
201
|
+
if (file) {
|
|
202
|
+
const doc = JSON.parse(readFile(file));
|
|
203
|
+
if (Array.isArray(doc)) {
|
|
204
|
+
const arrayProp = Object.keys(props).find((k) => props[k]?.type === 'array');
|
|
205
|
+
if (!arrayProp) {
|
|
206
|
+
throw new UsageError(`${cliName(tool)} takes no list argument — --file must hold a JSON object`);
|
|
207
|
+
}
|
|
208
|
+
args[arrayProp] = doc;
|
|
209
|
+
}
|
|
210
|
+
else if (doc && typeof doc === 'object') {
|
|
211
|
+
Object.assign(args, doc);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
throw new UsageError('--file must hold a JSON object or array');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
for (const [key, schema] of Object.entries(props)) {
|
|
218
|
+
if (key === 'file')
|
|
219
|
+
continue;
|
|
220
|
+
const values = argv.repeats[key];
|
|
221
|
+
if (values?.length) {
|
|
222
|
+
args[key] = coerce(schema, values);
|
|
223
|
+
}
|
|
224
|
+
else if (schema?.type === 'boolean' && argv.flags[key] === true) {
|
|
225
|
+
args[key] = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
pathParams(tool).forEach((param, i) => {
|
|
229
|
+
if (args[param] === undefined && positionals[i] !== undefined) {
|
|
230
|
+
args[param] = positionals[i];
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
const missing = (tool.inputSchema.required ?? []).filter((k) => args[k] === undefined);
|
|
234
|
+
if (missing.length) {
|
|
235
|
+
throw new UsageError(`${cliName(tool)} is missing: ${missing.join(', ')}\n\n${toolUsage(tool)}`);
|
|
236
|
+
}
|
|
237
|
+
return args;
|
|
238
|
+
}
|
|
239
|
+
/** One tool's usage line, derived from its schema rather than written by hand. */
|
|
240
|
+
function toolUsage(tool, bin = 'rcs') {
|
|
241
|
+
const props = (tool.inputSchema.properties ?? {});
|
|
242
|
+
const required = new Set(tool.inputSchema.required ?? []);
|
|
243
|
+
const params = pathParams(tool);
|
|
244
|
+
const flags = Object.keys(props)
|
|
245
|
+
.filter((k) => !params.includes(k))
|
|
246
|
+
.map((k) => {
|
|
247
|
+
// A nullable argument declares `["string","null"]`; read it as string|null.
|
|
248
|
+
const raw = props[k]?.type;
|
|
249
|
+
const type = Array.isArray(raw) ? raw.join('|') : (raw ?? 'value');
|
|
250
|
+
const one = type === 'boolean' ? `--${k}` : `--${k} <${type}>`;
|
|
251
|
+
return required.has(k) ? one : `[${one}]`;
|
|
252
|
+
});
|
|
253
|
+
return ` ${bin} ${[cliName(tool), ...params.map((p) => `<${p}>`), ...flags].join(' ')}`;
|
|
254
|
+
}
|
|
255
|
+
/** The generated half of the help: every catalog tool without a curated command. */
|
|
256
|
+
function catalogUsage(bin = 'rcs') {
|
|
257
|
+
return tools_1.TOOLS.filter((t) => !exports.CURATED.has(t.name))
|
|
258
|
+
.map((t) => `${toolUsage(t, bin)} # ${t.scope}`)
|
|
259
|
+
.join('\n');
|
|
260
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -41,6 +41,14 @@ export interface GatewayConfig {
|
|
|
41
41
|
* argv, a bundler-rewritten path, or a direct import all fall through to 'rcs'.
|
|
42
42
|
*/
|
|
43
43
|
export declare function detectSkin(argv?: readonly string[]): 'wa' | 'rcs';
|
|
44
|
+
/**
|
|
45
|
+
* The product a bin is branded as. The surveys packages (@wa-surveys/…,
|
|
46
|
+
* @rcs-surveys/…) are aliases like @wa-campaigns/…: same gateway, same catalog.
|
|
47
|
+
* The brand only changes the words the host and the model read; the channel
|
|
48
|
+
* default and env prefix still come from {@link detectSkin}.
|
|
49
|
+
*/
|
|
50
|
+
export type Brand = 'rcs-campaigns' | 'wa-campaigns' | 'rcs-surveys' | 'wa-surveys';
|
|
51
|
+
export declare function detectBrand(argv?: readonly string[]): Brand;
|
|
44
52
|
/**
|
|
45
53
|
* The version of the package the user actually installed, read at runtime rather
|
|
46
54
|
* than restated in a constant. A hardcoded copy silently goes stale the first
|
|
@@ -61,12 +69,22 @@ export declare function detectSkin(argv?: readonly string[]): 'wa' | 'rcs';
|
|
|
61
69
|
* and `dist/mcp.js` alike.
|
|
62
70
|
*/
|
|
63
71
|
export declare function readVersion(argv?: readonly string[]): string;
|
|
64
|
-
/**
|
|
65
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Human-readable product + channel names for the running bin. Accepts a skin
|
|
74
|
+
* ('wa' | 'rcs', meaning the campaigns brand) or a full brand.
|
|
75
|
+
*/
|
|
76
|
+
export declare function skinLabels(skinOrBrand?: 'wa' | 'rcs' | Brand): {
|
|
66
77
|
product: string;
|
|
67
78
|
channel: string;
|
|
68
|
-
slug:
|
|
79
|
+
slug: Brand;
|
|
69
80
|
};
|
|
81
|
+
/**
|
|
82
|
+
* The hosted production gateway. Both skins (wa and rcs) talk to this one
|
|
83
|
+
* deployment, so a customer only has to supply an agent key. Set
|
|
84
|
+
* WA_GATEWAY_URL / RCS_GATEWAY_URL (or --url) to point at dev or a local
|
|
85
|
+
* gateway instead.
|
|
86
|
+
*/
|
|
87
|
+
export declare const DEFAULT_GATEWAY_URL = "https://rcs-templates-api-dot-msg2ai-server.ue.r.appspot.com";
|
|
70
88
|
/**
|
|
71
89
|
* Normalize a channel from configuration or a tool argument. Accepts the channel
|
|
72
90
|
* enum itself (any case) AND a brand id — canonical or legacy — resolving it to
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,IAAI,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,4EAA4E;AAC5E,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,UAAU,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,IAAI,GAAG,KAAK,CAE/E;AAED;;;;;GAKG;AACH,MAAM,MAAM,KAAK,GAAG,eAAe,GAAG,cAAc,GAAG,aAAa,GAAG,YAAY,CAAC;AAEpF,wBAAgB,WAAW,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,KAAK,CAczE;AA8CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,MAAM,CAO1E;AASD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,GAAE,IAAI,GAAG,KAAK,GAAG,KAAqB;aAX/B,MAAM;aAAW,MAAM;UAAQ,KAAK;EAehF;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,iEACgC,CAAC;AA6BjE;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAMpE;AAOD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,8EAA8E;AAC9E,qBAAa,YAAa,SAAQ,KAAK;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;gBACN,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO;CAM/D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CA4B7E;AAED,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAElD,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa;IAI/D,+EAA+E;IACzE,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;CAmCvD"}
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.GatewayClient = exports.GatewayError = void 0;
|
|
3
|
+
exports.GatewayClient = exports.GatewayError = exports.DEFAULT_GATEWAY_URL = void 0;
|
|
4
4
|
exports.detectSkin = detectSkin;
|
|
5
|
+
exports.detectBrand = detectBrand;
|
|
5
6
|
exports.readVersion = readVersion;
|
|
6
7
|
exports.skinLabels = skinLabels;
|
|
7
8
|
exports.normalizeChannel = normalizeChannel;
|
|
@@ -18,8 +19,22 @@ const node_path_1 = require("node:path");
|
|
|
18
19
|
* argv, a bundler-rewritten path, or a direct import all fall through to 'rcs'.
|
|
19
20
|
*/
|
|
20
21
|
function detectSkin(argv = process.argv) {
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
return detectBrand(argv).startsWith('wa-') ? 'wa' : 'rcs';
|
|
23
|
+
}
|
|
24
|
+
function detectBrand(argv = process.argv) {
|
|
25
|
+
switch (invokedBinName(argv)) {
|
|
26
|
+
case 'wa':
|
|
27
|
+
case 'wa-mcp':
|
|
28
|
+
return 'wa-campaigns';
|
|
29
|
+
case 'wa-surveys':
|
|
30
|
+
case 'wa-surveys-mcp':
|
|
31
|
+
return 'wa-surveys';
|
|
32
|
+
case 'rcs-surveys':
|
|
33
|
+
case 'rcs-surveys-mcp':
|
|
34
|
+
return 'rcs-surveys';
|
|
35
|
+
default:
|
|
36
|
+
return 'rcs-campaigns';
|
|
37
|
+
}
|
|
23
38
|
}
|
|
24
39
|
/**
|
|
25
40
|
* The bin basename this process was launched as — "wa-mcp", "mcp", "cli" — or
|
|
@@ -42,7 +57,18 @@ function invokedBinName(argv) {
|
|
|
42
57
|
* — hence "mcp" and "cli" rather than "rcs-mcp" and "rcs". Anything not in this
|
|
43
58
|
* set is not one of ours, and is never trusted to locate a package.
|
|
44
59
|
*/
|
|
45
|
-
const OWN_BINS = new Set([
|
|
60
|
+
const OWN_BINS = new Set([
|
|
61
|
+
'wa',
|
|
62
|
+
'wa-mcp',
|
|
63
|
+
'rcs',
|
|
64
|
+
'rcs-mcp',
|
|
65
|
+
'mcp',
|
|
66
|
+
'cli',
|
|
67
|
+
'wa-surveys',
|
|
68
|
+
'wa-surveys-mcp',
|
|
69
|
+
'rcs-surveys',
|
|
70
|
+
'rcs-surveys-mcp'
|
|
71
|
+
]);
|
|
46
72
|
/** The `version` of the package.json in `dir`, or undefined if unreadable. */
|
|
47
73
|
function packageVersionAt(dir) {
|
|
48
74
|
try {
|
|
@@ -81,12 +107,27 @@ function readVersion(argv = process.argv) {
|
|
|
81
107
|
}
|
|
82
108
|
return packageVersionAt((0, node_path_1.join)(__dirname, '..')) ?? '0.0.0';
|
|
83
109
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
110
|
+
const BRAND_LABELS = {
|
|
111
|
+
'wa-campaigns': { product: 'WhatsApp Campaigns', channel: 'WhatsApp', slug: 'wa-campaigns' },
|
|
112
|
+
'rcs-campaigns': { product: 'RCS Campaigns', channel: 'RCS', slug: 'rcs-campaigns' },
|
|
113
|
+
'wa-surveys': { product: 'WhatsApp Surveys', channel: 'WhatsApp', slug: 'wa-surveys' },
|
|
114
|
+
'rcs-surveys': { product: 'RCS Surveys', channel: 'RCS', slug: 'rcs-surveys' }
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Human-readable product + channel names for the running bin. Accepts a skin
|
|
118
|
+
* ('wa' | 'rcs', meaning the campaigns brand) or a full brand.
|
|
119
|
+
*/
|
|
120
|
+
function skinLabels(skinOrBrand = detectBrand()) {
|
|
121
|
+
const brand = skinOrBrand === 'wa' ? 'wa-campaigns' : skinOrBrand === 'rcs' ? 'rcs-campaigns' : skinOrBrand;
|
|
122
|
+
return { ...BRAND_LABELS[brand] };
|
|
89
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* The hosted production gateway. Both skins (wa and rcs) talk to this one
|
|
126
|
+
* deployment, so a customer only has to supply an agent key. Set
|
|
127
|
+
* WA_GATEWAY_URL / RCS_GATEWAY_URL (or --url) to point at dev or a local
|
|
128
|
+
* gateway instead.
|
|
129
|
+
*/
|
|
130
|
+
exports.DEFAULT_GATEWAY_URL = 'https://rcs-templates-api-dot-msg2ai-server.ue.r.appspot.com';
|
|
90
131
|
/** First environment variable that is set and non-empty, under either prefix. */
|
|
91
132
|
function fromEnv(...names) {
|
|
92
133
|
for (const name of names) {
|
|
@@ -159,7 +200,7 @@ function resolveConfig(partial) {
|
|
|
159
200
|
const [keyFirst, keySecond] = skin === 'wa'
|
|
160
201
|
? ['WA_AGENT_KEY', 'RCS_AGENT_KEY']
|
|
161
202
|
: ['RCS_AGENT_KEY', 'WA_AGENT_KEY'];
|
|
162
|
-
const baseUrl = partial?.baseUrl ?? fromEnv(urlFirst, urlSecond) ??
|
|
203
|
+
const baseUrl = partial?.baseUrl ?? fromEnv(urlFirst, urlSecond) ?? exports.DEFAULT_GATEWAY_URL;
|
|
163
204
|
const apiKey = partial?.apiKey ?? fromEnv(keyFirst, keySecond) ?? '';
|
|
164
205
|
if (!apiKey) {
|
|
165
206
|
throw new Error(`No agent key configured. Set ${keyFirst} or pass apiKey explicitly.`);
|
package/dist/mcp.js
CHANGED
|
@@ -11,13 +11,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
11
11
|
* runtime dependencies — nothing to install, nothing to drift, and it slots into
|
|
12
12
|
* the workspace without touching the lockfile's external graph.
|
|
13
13
|
*
|
|
14
|
-
* Credentials: the agent key
|
|
15
|
-
*
|
|
14
|
+
* Credentials: the agent key comes from the environment (RCS_AGENT_KEY); the
|
|
15
|
+
* gateway URL defaults to the hosted production gateway and can be overridden
|
|
16
|
+
* with RCS_GATEWAY_URL. This process holds the key only to forward
|
|
16
17
|
* it as a Bearer token to the gateway — all business logic stays server-side.
|
|
17
18
|
*
|
|
18
19
|
* Wire it up (e.g. Claude Code):
|
|
19
20
|
* claude mcp add rcs-campaigns -- npx -y -p @rcs-campaigns/agent-toolkit rcs-mcp
|
|
20
|
-
* (with RCS_AGENT_KEY
|
|
21
|
+
* (with RCS_AGENT_KEY set in the environment)
|
|
21
22
|
*/
|
|
22
23
|
const node_readline_1 = require("node:readline");
|
|
23
24
|
const client_1 = require("./client");
|
|
@@ -31,7 +32,7 @@ const PROTOCOL_VERSION = '2024-11-05';
|
|
|
31
32
|
* Same catalog, same gateway — only the words the model reads differ.
|
|
32
33
|
*/
|
|
33
34
|
const SKIN = (0, client_1.detectSkin)();
|
|
34
|
-
const { product: PRODUCT, channel: CHANNEL, slug: SLUG } = (0, client_1.skinLabels)(
|
|
35
|
+
const { product: PRODUCT, channel: CHANNEL, slug: SLUG } = (0, client_1.skinLabels)((0, client_1.detectBrand)());
|
|
35
36
|
const SERVER_INFO = { name: SLUG, version: (0, client_1.readVersion)() };
|
|
36
37
|
/**
|
|
37
38
|
* Surfaced to MCP hosts as the initialize result's `instructions` string — hosts
|
package/dist/tools.js
CHANGED
|
@@ -102,7 +102,7 @@ exports.TOOLS = [
|
|
|
102
102
|
{
|
|
103
103
|
name: 'search_templates',
|
|
104
104
|
description: 'Search the published RCS template marketplace. Returns normalized listings (id, name, rating, cloneCount, categoryKeys).',
|
|
105
|
-
scope: '
|
|
105
|
+
scope: 'campaigns:read',
|
|
106
106
|
method: 'GET',
|
|
107
107
|
path: '/templates',
|
|
108
108
|
inputSchema: {
|
|
@@ -139,7 +139,7 @@ exports.TOOLS = [
|
|
|
139
139
|
{
|
|
140
140
|
name: 'get_template',
|
|
141
141
|
description: 'Fetch one template by id (a published marketplace listing or one your org owns).',
|
|
142
|
-
scope: '
|
|
142
|
+
scope: 'campaigns:read',
|
|
143
143
|
method: 'GET',
|
|
144
144
|
path: '/templates/:id',
|
|
145
145
|
inputSchema: {
|
|
@@ -156,7 +156,7 @@ exports.TOOLS = [
|
|
|
156
156
|
{
|
|
157
157
|
name: 'render_template',
|
|
158
158
|
description: 'Validate a template and return the rendered RCS payload + variable-bound SMS/MMS fallback, without sending. Use to inspect before cloning/sending.',
|
|
159
|
-
scope: '
|
|
159
|
+
scope: 'campaigns:read',
|
|
160
160
|
method: 'POST',
|
|
161
161
|
path: '/templates/:id/render',
|
|
162
162
|
inputSchema: {
|
|
@@ -181,7 +181,7 @@ exports.TOOLS = [
|
|
|
181
181
|
{
|
|
182
182
|
name: 'create_template',
|
|
183
183
|
description: 'Create a template draft in your org. Compose a TemplateContent (text/card/carousel) with a REQUIRED non-empty smsFallback; the shared RCS validator gates it. Submit it (submit_template) and await provider approval before send_message for rich RCS; an unsubmitted template delivers as SMS fallback.',
|
|
184
|
-
scope: '
|
|
184
|
+
scope: 'campaigns:write',
|
|
185
185
|
method: 'POST',
|
|
186
186
|
path: '/templates',
|
|
187
187
|
successStatus: 201,
|
|
@@ -220,7 +220,7 @@ exports.TOOLS = [
|
|
|
220
220
|
{
|
|
221
221
|
name: 'update_template',
|
|
222
222
|
description: 'Partially update a template your org owns: name, description, category, tags, knowledgeDoc, and content (re-validated by the shared RCS validator — a non-empty smsFallback is still required). Only the fields you pass change. Editing the CONTENT of an already-approved template invalidates that approval: submit_template it again and await approval, or it delivers as SMS fallback until then. Use get_template first to edit incrementally rather than overwriting blind.',
|
|
223
|
-
scope: '
|
|
223
|
+
scope: 'campaigns:write',
|
|
224
224
|
method: 'PUT',
|
|
225
225
|
path: '/templates/:id',
|
|
226
226
|
inputSchema: {
|
|
@@ -265,7 +265,7 @@ exports.TOOLS = [
|
|
|
265
265
|
{
|
|
266
266
|
name: 'duplicate_template',
|
|
267
267
|
description: 'Duplicate a template your org OWNS into a fresh private draft (e.g. to iterate a variant without touching the original). The copy keeps the content, channel, category, tags and knowledge base, but starts unsubmitted — it has no provider approval of its own until you submit_template it. For copying someone else\'s PUBLISHED marketplace listing, use clone_template instead.',
|
|
268
|
-
scope: '
|
|
268
|
+
scope: 'campaigns:write',
|
|
269
269
|
method: 'POST',
|
|
270
270
|
path: '/templates/:id/duplicate',
|
|
271
271
|
successStatus: 201,
|
|
@@ -283,7 +283,7 @@ exports.TOOLS = [
|
|
|
283
283
|
{
|
|
284
284
|
name: 'delete_template',
|
|
285
285
|
description: 'Delete a template your org owns. IRREVERSIBLE, and it also removes the template from the provider (Twilio), so an approved template loses its approval and can no longer be sent. Broadcasts that already went out are unaffected. Prefer this only for drafts and abandoned iterations; ask the user before deleting anything they have submitted or sent.',
|
|
286
|
-
scope: '
|
|
286
|
+
scope: 'campaigns:write',
|
|
287
287
|
method: 'DELETE',
|
|
288
288
|
path: '/templates/:id',
|
|
289
289
|
inputSchema: {
|
|
@@ -318,7 +318,7 @@ exports.TOOLS = [
|
|
|
318
318
|
{
|
|
319
319
|
name: 'submit_template',
|
|
320
320
|
description: 'Submit an org-owned template to the provider (Twilio) for approval. Returns the contentSid + approval status.',
|
|
321
|
-
scope: '
|
|
321
|
+
scope: 'campaigns:submit',
|
|
322
322
|
method: 'POST',
|
|
323
323
|
path: '/templates/:id/submit',
|
|
324
324
|
inputSchema: {
|
|
@@ -685,7 +685,7 @@ exports.TOOLS = [
|
|
|
685
685
|
{
|
|
686
686
|
name: 'get_knowledge',
|
|
687
687
|
description: "Read a template's knowledge base: the grounding document text plus whether answering is enabled. Use before set_knowledge to edit a doc incrementally instead of overwriting it blind.",
|
|
688
|
-
scope: '
|
|
688
|
+
scope: 'campaigns:read',
|
|
689
689
|
method: 'GET',
|
|
690
690
|
path: '/templates/:id/knowledge',
|
|
691
691
|
inputSchema: {
|
|
@@ -702,7 +702,7 @@ exports.TOOLS = [
|
|
|
702
702
|
{
|
|
703
703
|
name: 'set_knowledge',
|
|
704
704
|
description: 'Set/replace a template\'s knowledge base: the grounding document AI answers are generated from when recipients reply. Include operating instructions in the doc itself — e.g. "Any question not answered by this document must be answered exactly: …". Setting a doc re-enables answering.',
|
|
705
|
-
scope: '
|
|
705
|
+
scope: 'campaigns:write',
|
|
706
706
|
method: 'PUT',
|
|
707
707
|
path: '/templates/:id/knowledge',
|
|
708
708
|
inputSchema: {
|
|
@@ -726,7 +726,7 @@ exports.TOOLS = [
|
|
|
726
726
|
{
|
|
727
727
|
name: 'delete_knowledge',
|
|
728
728
|
description: 'Delete a template\'s knowledge base document.',
|
|
729
|
-
scope: '
|
|
729
|
+
scope: 'campaigns:write',
|
|
730
730
|
method: 'DELETE',
|
|
731
731
|
path: '/templates/:id/knowledge',
|
|
732
732
|
inputSchema: {
|
|
@@ -743,7 +743,7 @@ exports.TOOLS = [
|
|
|
743
743
|
{
|
|
744
744
|
name: 'enable_knowledge',
|
|
745
745
|
description: 'Enable knowledge answering on a template (the doc is used again).',
|
|
746
|
-
scope: '
|
|
746
|
+
scope: 'campaigns:write',
|
|
747
747
|
method: 'POST',
|
|
748
748
|
path: '/templates/:id/knowledge/enable',
|
|
749
749
|
inputSchema: {
|
|
@@ -760,7 +760,7 @@ exports.TOOLS = [
|
|
|
760
760
|
{
|
|
761
761
|
name: 'disable_knowledge',
|
|
762
762
|
description: 'Disable knowledge answering on a template WITHOUT deleting the doc (pause).',
|
|
763
|
-
scope: '
|
|
763
|
+
scope: 'campaigns:write',
|
|
764
764
|
method: 'POST',
|
|
765
765
|
path: '/templates/:id/knowledge/disable',
|
|
766
766
|
inputSchema: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rcs-campaigns/agent-toolkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"mcpName": "com.rcs-campaigns/campaigns",
|
|
5
5
|
"description": "Agent skins for the RCS Campaigns gateway: an MCP server (rcs-mcp / wa-mcp) and a CLI (rcs / wa) over one shared client.",
|
|
6
6
|
"license": "UNLICENSED",
|