@wirecat/cli-core 0.586.0 → 0.588.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.
@@ -1535,14 +1535,22 @@ export const orders = sqliteTable("orders", {
1535
1535
  index("orders_by_author_type_author_id").on(table.authorType, table.authorId),
1536
1536
  index("orders_by_confirmed_by_type_confirmed_by_id").on(table.confirmedByType, table.confirmedById),
1537
1537
  ]);
1538
- /** A sales pipeline of the owner's: the stages a deal goes through, in order. */
1538
+ /** A sales pipeline: the stages a deal or a lead goes through, in order; the owner's own or a CRM account's. */
1539
1539
  export const pipelines = sqliteTable("pipelines", {
1540
1540
  id: integer("id").primaryKey(),
1541
+ accountId: integer("account_id").references(() => accounts.id),
1542
+ externalId: text("external_id"),
1543
+ kind: text("kind").notNull().default("deal"),
1541
1544
  name: text("name").notNull(),
1542
- nameFolded: text("name_folded").notNull().unique(),
1545
+ nameFolded: text("name_folded").notNull(),
1543
1546
  createdAt: integer("created_at").notNull(),
1544
1547
  updatedAt: integer("updated_at").notNull(),
1545
- });
1548
+ }, (table) => [
1549
+ uniqueIndex("pipelines_by_account_id_external_id")
1550
+ .on(table.accountId, table.externalId)
1551
+ .where(sql `account_id IS NOT NULL AND external_id IS NOT NULL`),
1552
+ uniqueIndex("pipelines_by_name_folded_kind").on(table.nameFolded, table.kind).where(sql `account_id IS NULL`),
1553
+ ]);
1546
1554
  export const pipelineStages = sqliteTable("pipeline_stages", {
1547
1555
  id: integer("id").primaryKey(),
1548
1556
  pipelineId: integer("pipeline_id")
@@ -1551,11 +1559,19 @@ export const pipelineStages = sqliteTable("pipeline_stages", {
1551
1559
  name: text("name").notNull(),
1552
1560
  nameFolded: text("name_folded").notNull(),
1553
1561
  position: integer("position").notNull(),
1562
+ outcome: text("outcome").notNull().default("open"),
1563
+ externalId: text("external_id"),
1554
1564
  createdAt: integer("created_at").notNull(),
1555
1565
  updatedAt: integer("updated_at").notNull(),
1556
- }, (table) => [unique().on(table.pipelineId, table.nameFolded), unique().on(table.pipelineId, table.position)]);
1566
+ }, (table) => [
1567
+ unique().on(table.pipelineId, table.nameFolded),
1568
+ uniqueIndex("pipeline_stages_by_pipeline_id_external_id")
1569
+ .on(table.pipelineId, table.externalId)
1570
+ .where(sql `external_id IS NOT NULL`),
1571
+ index("pipeline_stages_by_pipeline_id_position").on(table.pipelineId, table.position),
1572
+ ]);
1557
1573
  /** A deal: what the owner hopes to sell, where it stands in its pipeline, and whether it ended. */
1558
- export const opportunities = sqliteTable("opportunities", {
1574
+ export const deals = sqliteTable("deals", {
1559
1575
  id: integer("id").primaryKey(),
1560
1576
  title: text("title").notNull(),
1561
1577
  organizationId: integer("organization_id").references(() => organizations.id),
@@ -1565,27 +1581,83 @@ export const opportunities = sqliteTable("opportunities", {
1565
1581
  .references(() => pipelineStages.id),
1566
1582
  status: text("status").notNull(),
1567
1583
  currency: text("currency"),
1568
- /** Minor units of `currency`. */
1569
1584
  amount: integer("amount"),
1570
1585
  closeDate: text("close_date"),
1571
1586
  closedDate: text("closed_date"),
1572
- review: text("review").notNull(),
1587
+ rank: real("rank"),
1588
+ accountId: integer("account_id").references(() => accounts.id),
1589
+ externalId: text("external_id"),
1590
+ authorType: text("author_type").notNull(),
1591
+ authorId: integer("author_id").notNull(),
1592
+ model: text("model"),
1593
+ source: text("source").notNull(),
1594
+ metadata: text("metadata"),
1595
+ createdAt: integer("created_at").notNull(),
1596
+ updatedAt: integer("updated_at").notNull(),
1597
+ deletedAt: integer("deleted_at"),
1598
+ }, (table) => [
1599
+ uniqueIndex("deals_by_account_id_external_id")
1600
+ .on(table.accountId, table.externalId)
1601
+ .where(sql `account_id IS NOT NULL AND external_id IS NOT NULL`),
1602
+ index("deals_by_organization_id").on(table.organizationId),
1603
+ index("deals_by_project_id").on(table.projectId),
1604
+ index("deals_by_stage_id").on(table.stageId),
1605
+ index("deals_by_author_type_author_id").on(table.authorType, table.authorId),
1606
+ ]);
1607
+ /** A lead: a first contact not yet a deal, in a lead pipeline; `deal_id` is the deal it became. */
1608
+ export const leads = sqliteTable("leads", {
1609
+ id: integer("id").primaryKey(),
1610
+ title: text("title").notNull(),
1611
+ organizationId: integer("organization_id").references(() => organizations.id),
1612
+ projectId: integer("project_id").references(() => projects.id),
1613
+ stageId: integer("stage_id")
1614
+ .notNull()
1615
+ .references(() => pipelineStages.id),
1616
+ status: text("status").notNull(),
1617
+ currency: text("currency"),
1618
+ amount: integer("amount"),
1619
+ closeDate: text("close_date"),
1620
+ closedDate: text("closed_date"),
1621
+ rank: real("rank"),
1622
+ dealId: integer("deal_id").references(() => deals.id),
1623
+ accountId: integer("account_id").references(() => accounts.id),
1624
+ externalId: text("external_id"),
1573
1625
  authorType: text("author_type").notNull(),
1574
1626
  authorId: integer("author_id").notNull(),
1575
1627
  model: text("model"),
1576
- confirmedByType: text("confirmed_by_type"),
1577
- confirmedById: integer("confirmed_by_id"),
1578
1628
  source: text("source").notNull(),
1579
1629
  metadata: text("metadata"),
1580
1630
  createdAt: integer("created_at").notNull(),
1581
1631
  updatedAt: integer("updated_at").notNull(),
1582
1632
  deletedAt: integer("deleted_at"),
1583
1633
  }, (table) => [
1584
- index("opportunities_by_organization_id").on(table.organizationId),
1585
- index("opportunities_by_project_id").on(table.projectId),
1586
- index("opportunities_by_stage_id").on(table.stageId),
1587
- index("opportunities_by_author_type_author_id").on(table.authorType, table.authorId),
1588
- index("opportunities_by_confirmed_by_type_confirmed_by_id").on(table.confirmedByType, table.confirmedById),
1634
+ uniqueIndex("leads_by_account_id_external_id")
1635
+ .on(table.accountId, table.externalId)
1636
+ .where(sql `account_id IS NOT NULL AND external_id IS NOT NULL`),
1637
+ index("leads_by_organization_id").on(table.organizationId),
1638
+ index("leads_by_project_id").on(table.projectId),
1639
+ index("leads_by_stage_id").on(table.stageId),
1640
+ index("leads_by_deal_id").on(table.dealId),
1641
+ index("leads_by_author_type_author_id").on(table.authorType, table.authorId),
1642
+ ]);
1643
+ /** Each stage a deal or a lead entered and when, written by triggers, so conversion between stages can be counted. */
1644
+ export const pipelineStageChanges = sqliteTable("pipeline_stage_changes", {
1645
+ id: integer("id").primaryKey(),
1646
+ itemType: text("item_type").notNull(),
1647
+ itemId: integer("item_id").notNull(),
1648
+ pipelineId: integer("pipeline_id")
1649
+ .notNull()
1650
+ .references(() => pipelines.id),
1651
+ fromStageId: integer("from_stage_id").references(() => pipelineStages.id),
1652
+ toStageId: integer("to_stage_id")
1653
+ .notNull()
1654
+ .references(() => pipelineStages.id),
1655
+ changedAt: integer("changed_at").notNull(),
1656
+ }, (table) => [
1657
+ index("pipeline_stage_changes_by_item_type_item_id").on(table.itemType, table.itemId),
1658
+ index("pipeline_stage_changes_by_pipeline_id").on(table.pipelineId),
1659
+ index("pipeline_stage_changes_by_from_stage_id").on(table.fromStageId),
1660
+ index("pipeline_stage_changes_by_to_stage_id").on(table.toStageId),
1589
1661
  ]);
1590
1662
  /** What a source said about one of its records, as it said it: the layer under the entity an import makes. */
1591
1663
  export const records = sqliteTable("records", {
@@ -21,6 +21,8 @@ const REFERABLE = {
21
21
  payment: { tombstone: true },
22
22
  order: { tombstone: true },
23
23
  deal: { tombstone: true },
24
+ lead: { tombstone: true },
25
+ pipeline: { tombstone: false },
24
26
  message: { tombstone: true },
25
27
  email: { tombstone: true },
26
28
  email_thread: { tombstone: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.586.0",
3
+ "version": "0.588.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",
@@ -258,7 +258,7 @@
258
258
  "@types/node": "^22.20.5",
259
259
  "@types/pngjs": "6.0.5",
260
260
  "@vitest/coverage-v8": "^5.0.5",
261
- "@wirecat/cli-base": "0.79.0",
261
+ "@wirecat/cli-base": "0.80.0",
262
262
  "@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
263
263
  "commander": "15.0.0",
264
264
  "cspell": "10.4.0",
@@ -278,7 +278,7 @@
278
278
  },
279
279
  "peerDependencies": {
280
280
  "@napi-rs/canvas": ">=0.1.69 <2",
281
- "@wirecat/cli-base": ">=0.76.0 <0.80.0",
281
+ "@wirecat/cli-base": ">=0.76.0 <0.81.0",
282
282
  "commander": ">=15"
283
283
  },
284
284
  "bin": {
@@ -1,4 +0,0 @@
1
- import type { MessageStore } from "../../../store/index.js";
2
- import type { ProposalKind } from "../types.js";
3
- /** An agent's deal, by its own ref (`deal:12`) as orders are; only the owner decides it. */
4
- export declare const dealKind: (store: Pick<MessageStore, "deals">) => ProposalKind;
@@ -1,32 +0,0 @@
1
- import { kept, proposerOfAuthor, pushed } from "./shared.js";
2
- const rowOf = (deal) => ({
3
- id: deal.ref,
4
- kind: "deal",
5
- status: deal.review,
6
- project: deal.project,
7
- subject: deal.ref,
8
- object: deal.organization?.ref ?? null,
9
- summary: `Deal ${deal.summary}`,
10
- proposedBy: proposerOfAuthor(deal.author),
11
- reason: deal.reason,
12
- evidence: [],
13
- score: null,
14
- signals: null,
15
- parts: null,
16
- createdAt: deal.createdAt,
17
- });
18
- /** An agent's deal, by its own ref (`deal:12`) as orders are; only the owner decides it. */
19
- export const dealKind = (store) => ({
20
- kind: "deal",
21
- prefix: "deal",
22
- list: async (filter) => {
23
- const status = filter.status ?? "proposed";
24
- const deals = await store.deals.list({ ...pushed(filter), ...(status === "all" ? {} : { review: status }) });
25
- return kept(deals.map(rowOf), filter);
26
- },
27
- get: async (id) => rowOf(await store.deals.get(id)),
28
- decide: async (id, { confirm, by, reason }) => {
29
- const deal = confirm ? await store.deals.confirm(id, by, reason) : await store.deals.reject(id, by, reason);
30
- return { id, kind: "deal", status: confirm ? "confirmed" : "rejected", made: confirm ? [deal.ref] : [] };
31
- },
32
- });