@crmforall/connector 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.
@@ -18,6 +18,7 @@ export declare const MCP_TOOL: {
18
18
  readonly TRACK_ENGAGEMENT: "track_engagement";
19
19
  readonly ONBOARDING_STATUS: "onboarding_status";
20
20
  readonly SYNC_ALL: "sync_all";
21
+ readonly AUDIENCE_SUMMARY: "audience_summary";
21
22
  };
22
23
  export type McpToolName = (typeof MCP_TOOL)[keyof typeof MCP_TOOL];
23
24
  export declare const APPROVAL_SCOPE: {
package/dist/contracts.js CHANGED
@@ -21,6 +21,7 @@ exports.MCP_TOOL = {
21
21
  TRACK_ENGAGEMENT: "track_engagement",
22
22
  ONBOARDING_STATUS: "onboarding_status",
23
23
  SYNC_ALL: "sync_all",
24
+ AUDIENCE_SUMMARY: "audience_summary",
24
25
  };
25
26
  exports.APPROVAL_SCOPE = {
26
27
  TARGET_SNAPSHOT_CREATE: "target_snapshot:create",
@@ -45,6 +46,7 @@ exports.TOOL_APPROVAL_REQUIREMENT = {
45
46
  [exports.MCP_TOOL.TRACK_ENGAGEMENT]: null,
46
47
  [exports.MCP_TOOL.ONBOARDING_STATUS]: null,
47
48
  [exports.MCP_TOOL.SYNC_ALL]: null,
49
+ [exports.MCP_TOOL.AUDIENCE_SUMMARY]: null,
48
50
  };
49
51
  /** 승인 토큰이 필요한 도구 — 토큰 없이 호출되면 즉시 거부한다. */
50
52
  exports.APPROVAL_REQUIRED_TOOLS = new Set(Object.entries(exports.TOOL_APPROVAL_REQUIREMENT)
package/dist/server.js CHANGED
@@ -15,8 +15,23 @@ const tools_1 = require("./tools");
15
15
  async function main() {
16
16
  const config = (0, config_1.loadConfig)();
17
17
  if (!config) {
18
- console.error("[crmforall-connector] 설정이 없습니다. 먼저 `npx @crmforall/connect init`을 실행하세요.");
19
- process.exit(1);
18
+ // 설정 전(플러그인 설치 직후 등) 죽지 않고 '설치 안내 모드'로 떠서 다음 단계를 알려준다.
19
+ const setup = new mcp_js_1.McpServer({ name: "crmforall-connector", version: "0.1.9" }, { instructions: "아직 DB 설정이 없습니다. 터미널에서 `npx @crmforall/connect init`을 먼저 실행해 연결하세요." });
20
+ setup.tool("onboarding_status", "온보딩 상태 — 아직 설정(연결) 전입니다.", {}, async () => ({
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: JSON.stringify({
25
+ ready: false,
26
+ nextStep: "터미널에서 `npx @crmforall/connect init --token=<설치토큰> --platform-url=https://crmforall-console.vercel.app` 을 실행해 DB를 연결한 뒤, 이 세션을 재시작하세요.",
27
+ docs: "https://crmforall-console.vercel.app/docs/dev/connector",
28
+ }),
29
+ },
30
+ ],
31
+ }));
32
+ await setup.connect(new stdio_js_1.StdioServerTransport());
33
+ console.error("[crmforall-connector] 설정 없음 — 설치 안내 모드로 시작 (connect init 필요)");
34
+ return;
20
35
  }
21
36
  let db = null;
22
37
  const getDb = async () => {
@@ -29,7 +44,7 @@ async function main() {
29
44
  };
30
45
  const server = new mcp_js_1.McpServer({
31
46
  name: "crmforall-connector",
32
- version: "0.1.0",
47
+ version: "0.1.9",
33
48
  }, {
34
49
  instructions: [
35
50
  "크렘포올 DB 커넥터입니다. 고객사 DB를 진단·연결·매핑해 크렘포올 플랫폼과 잇습니다.",
package/dist/targets.d.ts CHANGED
@@ -21,6 +21,20 @@ export interface TargetComputation {
21
21
  }>;
22
22
  previewHash: string;
23
23
  }
24
+ export interface AudienceSummary {
25
+ totalCustomers: number;
26
+ withPhone: number | null;
27
+ optInTrue: number | null;
28
+ optInFalse: number | null;
29
+ optInUnknown: number | null;
30
+ /** 발송 가능 후보(전화 보유 + 동의 거부 아님) — 첫 체감용 대략치 */
31
+ reachable: number | null;
32
+ }
33
+ /**
34
+ * 청중 요약 — 매핑만으로 즉시 가능한 "고객 N명 · 발송 동의 분포" (프로젝트 불필요).
35
+ * 온보딩 직후 첫 체감("보낼 수 있는 고객 N명")을 주기 위한 읽기 전용 집계.
36
+ */
37
+ export declare function audienceSummary(db: SqlExecutor, mapping: MappingConfig): Promise<AudienceSummary>;
24
38
  /**
25
39
  * 스냅샷/미리보기 해시 — 같은 대상 집합이면 항상 같은 값.
26
40
  * 승인 토큰의 snapshotHash 바인딩과 비교되므로 결정적이어야 한다.
package/dist/targets.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.audienceSummary = audienceSummary;
3
4
  exports.computePreviewHash = computePreviewHash;
4
5
  exports.computeTargets = computeTargets;
5
6
  const node_crypto_1 = require("node:crypto");
@@ -13,6 +14,38 @@ function ident(name) {
13
14
  function tableRef(t) {
14
15
  return `${ident(t.schema)}.${ident(t.table)}`;
15
16
  }
17
+ /**
18
+ * 청중 요약 — 매핑만으로 즉시 가능한 "고객 N명 · 발송 동의 분포" (프로젝트 불필요).
19
+ * 온보딩 직후 첫 체감("보낼 수 있는 고객 N명")을 주기 위한 읽기 전용 집계.
20
+ */
21
+ async function audienceSummary(db, mapping) {
22
+ const c = mapping.customer;
23
+ const hasPhone = Boolean(c.fields.phoneNumberHash);
24
+ const hasOptIn = Boolean(c.fields.marketingOptIn);
25
+ const selects = ["COUNT(*)::int AS total"];
26
+ if (hasPhone)
27
+ selects.push(`COUNT(${ident(c.fields.phoneNumberHash)})::int AS with_phone`);
28
+ if (hasOptIn) {
29
+ const o = ident(c.fields.marketingOptIn);
30
+ selects.push(`COUNT(*) FILTER (WHERE ${o} IS TRUE)::int AS optin_true`);
31
+ selects.push(`COUNT(*) FILTER (WHERE ${o} IS FALSE)::int AS optin_false`);
32
+ selects.push(`COUNT(*) FILTER (WHERE ${o} IS NULL)::int AS optin_null`);
33
+ if (hasPhone) {
34
+ selects.push(`COUNT(*) FILTER (WHERE ${o} IS DISTINCT FROM FALSE AND ${ident(c.fields.phoneNumberHash)} IS NOT NULL)::int AS reachable`);
35
+ }
36
+ }
37
+ const rows = (await db.selectRows(`SELECT ${selects.join(", ")} FROM ${tableRef(c.table)}`));
38
+ const row = rows[0] ?? {};
39
+ const num = (k) => (typeof row[k] === "number" ? row[k] : null);
40
+ return {
41
+ totalCustomers: num("total") ?? 0,
42
+ withPhone: hasPhone ? num("with_phone") : null,
43
+ optInTrue: hasOptIn ? num("optin_true") : null,
44
+ optInFalse: hasOptIn ? num("optin_false") : null,
45
+ optInUnknown: hasOptIn ? num("optin_null") : null,
46
+ reachable: hasOptIn && hasPhone ? num("reachable") : null,
47
+ };
48
+ }
16
49
  /**
17
50
  * 스냅샷/미리보기 해시 — 같은 대상 집합이면 항상 같은 값.
18
51
  * 승인 토큰의 snapshotHash 바인딩과 비교되므로 결정적이어야 한다.
package/dist/tools.js CHANGED
@@ -357,6 +357,22 @@ function registerTools(server, rt) {
357
357
  return unexpected(err);
358
358
  }
359
359
  });
360
+ server.tool(contracts_1.MCP_TOOL.AUDIENCE_SUMMARY, "확정된 매핑으로 고객 수·발송 동의 분포·발송 가능 후보 수를 즉시 집계한다 (프로젝트 불필요, 읽기 전용, 승인 불필요). 온보딩 직후 첫 체감용.", {}, async () => {
361
+ try {
362
+ if (!rt.config.mapping) {
363
+ return ok({
364
+ ready: false,
365
+ message: "먼저 propose_field_mapping → validate_mapping(persist=true)으로 매핑을 확정하세요.",
366
+ });
367
+ }
368
+ const db = await rt.getDb();
369
+ const summary = await (0, targets_1.audienceSummary)(db, rt.config.mapping);
370
+ return ok(summary);
371
+ }
372
+ catch (err) {
373
+ return unexpected(err);
374
+ }
375
+ });
360
376
  server.tool(contracts_1.MCP_TOOL.SYNC_SEND_RESULT, "발송 작업 결과를 플랫폼에서 가져와 CRM 스키마에 동기화한다 (승인 불필요)", { sendJobId: zod_1.z.string() }, async ({ sendJobId }) => {
361
377
  try {
362
378
  const db = await rt.getDb();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crmforall/connector",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "crmforall MCP 커넥터 — 운영 DB 읽기, CRM 스키마 쓰기(승인 토큰 필수), 대상 산출",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",