@jclvsh/iris 0.2.4 → 0.3.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/dist/client.js CHANGED
@@ -1,3 +1,32 @@
1
+ function safeStringify(value) {
2
+ if (Array.isArray(value)) {
3
+ const parts = value
4
+ .map((entry) => {
5
+ if (typeof entry === "string")
6
+ return entry;
7
+ if (entry && typeof entry === "object") {
8
+ const msg = entry.msg;
9
+ if (typeof msg === "string")
10
+ return msg;
11
+ }
12
+ try {
13
+ return JSON.stringify(entry);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ })
19
+ .filter((p) => typeof p === "string" && p.length > 0);
20
+ if (parts.length > 0)
21
+ return parts.join("; ");
22
+ }
23
+ try {
24
+ return JSON.stringify(value);
25
+ }
26
+ catch {
27
+ return String(value);
28
+ }
29
+ }
1
30
  export class IrisClient {
2
31
  apiKey;
3
32
  baseUrl;
@@ -77,7 +106,13 @@ export class IrisClient {
77
106
  try {
78
107
  const body = await response.json();
79
108
  if (body?.error) {
80
- const parts = [body.error.message || "unknown error"];
109
+ const rawMessage = body.error.message;
110
+ const messageStr = typeof rawMessage === "string" && rawMessage.trim() !== ""
111
+ ? rawMessage
112
+ : rawMessage != null
113
+ ? safeStringify(rawMessage)
114
+ : "unknown error";
115
+ const parts = [messageStr];
81
116
  if (body.error.code)
82
117
  parts.unshift(`[${body.error.code}]`);
83
118
  if (body.error.field)
@@ -28,7 +28,7 @@ export function registerChannelCommands(program, client) {
28
28
  channels
29
29
  .command("send")
30
30
  .description("send a message through a specific channel in a conversation")
31
- .requiredOption("--channel <channel>", "channel: email, instagram, twitter, linkedin")
31
+ .requiredOption("--channel <channel>", "channel: email, instagram, twitter")
32
32
  .requiredOption("--conversation-id <id>", "conversation ID")
33
33
  .requiredOption("--recipient-id <id>", "recipient (email for email channel; platform handle/ID for social)")
34
34
  .requiredOption("-c, --content <text>", "message content")
@@ -11,7 +11,7 @@ export function registerConversationCommands(program, client) {
11
11
  .option("-s, --page-size <n>", "items per page (max 100)", "50")
12
12
  .option("--status <status>", "filter by status")
13
13
  .option("--campaign-id <id>", "filter by campaign")
14
- .option("--channel <channel>", "filter by channel: email, instagram, twitter, linkedin")
14
+ .option("--channel <channel>", "filter by channel: email, instagram, twitter")
15
15
  .action(async (opts) => {
16
16
  try {
17
17
  const params = {};
@@ -7,7 +7,7 @@ export function registerMessageCommands(program, client) {
7
7
  .option("-p, --page <n>", "page number", "1")
8
8
  .option("-s, --page-size <n>", "items per page (max 100)", "50")
9
9
  .option("--campaign-id <id>", "filter by campaign")
10
- .option("--channel <channel>", "filter by channel: email, instagram, twitter, linkedin")
10
+ .option("--channel <channel>", "filter by channel: email, instagram, twitter")
11
11
  .option("--status <status>", "queue status (default: pending_approval)")
12
12
  .action(async (opts) => {
13
13
  try {
@@ -178,7 +178,7 @@ export function registerCampaignTools(server, client) {
178
178
  industries: z.array(z.string()).optional().describe("target industries"),
179
179
  discovery_keywords: z.array(z.string()).optional().describe("keywords for contact discovery"),
180
180
  recommended_channels: z
181
- .array(z.enum(["email", "instagram", "twitter", "linkedin"]))
181
+ .array(z.enum(["email", "instagram", "twitter"]))
182
182
  .optional()
183
183
  .describe("recommended outreach channels"),
184
184
  }, async ({ id, ...body }) => {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- const CHANNELS = ["email", "instagram", "twitter", "linkedin"];
2
+ const CHANNELS = ["email", "instagram", "twitter"];
3
3
  export function registerChannelTools(server, client) {
4
4
  server.tool("list_connected_accounts", "list all connected social media accounts. requires channels:read scope.", {}, async () => {
5
5
  try {
@@ -1,11 +1,52 @@
1
1
  import { z } from "zod";
2
- const DISCOVERY_SOURCES = ["google_maps", "instagram", "twitter", "linkedin"];
2
+ const DISCOVERY_SOURCES = ["google_maps", "instagram", "twitter", "tiktok", "reddit"];
3
+ const PROFILE_TYPES = [
4
+ "local_business",
5
+ "content_creator",
6
+ "influencer",
7
+ "b2b_professional",
8
+ "ecommerce",
9
+ "saas_company",
10
+ "agency",
11
+ "freelancer",
12
+ "other",
13
+ ];
3
14
  export function registerDiscoveryTools(server, client) {
4
- server.tool("search_contacts", "search for new contacts using external data sources (Google Maps, social platforms). requires discovery:write scope.", {
5
- query: z.string().min(1).max(500).describe("search query"),
6
- source: z.enum(DISCOVERY_SOURCES).describe("data source to search"),
7
- max_results: z.number().int().min(1).max(100).optional().describe("maximum number of results (default 20)"),
8
- campaign_id: z.string().uuid().optional().describe("campaign to add discovered contacts to"),
15
+ server.tool("search_contacts", "kick off a multi-source contact discovery job (Apify-backed). returns a job_id and contacts when ready (waits up to 90s; if status=queued, poll get_discovery_job). closed source list: google_maps, instagram, twitter, tiktok, reddit. requires discovery:write scope.", {
16
+ keywords: z
17
+ .array(z.string().min(1))
18
+ .min(1)
19
+ .describe("non-empty array of search keywords (e.g. ['boutique hotel', 'rooftop venue'])"),
20
+ location: z
21
+ .string()
22
+ .optional()
23
+ .describe("location for geo-scoped goals (local_business / venue / brick-and-mortar). omit for global goals (creators, b2b, saas)."),
24
+ radius_miles: z.number().positive().optional().default(25).describe("search radius in miles (default 25)"),
25
+ max_results: z
26
+ .number()
27
+ .int()
28
+ .min(10)
29
+ .max(500)
30
+ .optional()
31
+ .describe("max contacts to return (10-500, default 50; server clamps to plan cap)"),
32
+ sources: z
33
+ .array(z.enum(DISCOVERY_SOURCES))
34
+ .optional()
35
+ .describe("override the default per-profile_type source mix. valid: google_maps, instagram, twitter, tiktok, reddit. omit to let the orchestrator pick."),
36
+ profile_type: z
37
+ .enum(PROFILE_TYPES)
38
+ .optional()
39
+ .describe("ICP profile type — drives default source mix when sources is omitted"),
40
+ campaign_id: z
41
+ .string()
42
+ .uuid()
43
+ .optional()
44
+ .describe("campaign to attach discovered contacts to on finalize"),
45
+ enrich_emails: z
46
+ .boolean()
47
+ .optional()
48
+ .default(true)
49
+ .describe("run waterfall email enrichment (BetterContact → FullEnrich → ZeroBounce). default true."),
9
50
  }, async (args) => {
10
51
  try {
11
52
  const result = await client.post("/discovery/search", args);
@@ -33,7 +74,7 @@ export function registerDiscoveryTools(server, client) {
33
74
  };
34
75
  }
35
76
  });
36
- server.tool("get_discovery_job", "get the status and results of a discovery job. requires discovery:read scope.", {
77
+ server.tool("get_discovery_job", "get the status, progress, and contacts of a discovery job. status values: pending, running, completed, failed. contacts populated only when status=completed. requires discovery:read scope.", {
37
78
  id: z.string().uuid().describe("discovery job ID"),
38
79
  }, async ({ id }) => {
39
80
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jclvsh/iris",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for the Iris API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",