@awesomate/sdk 0.34.0 → 0.45.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/index.d.ts CHANGED
@@ -26,7 +26,7 @@
26
26
  * Docs: https://hub.awesomate.ai/docs/sdk/
27
27
  */
28
28
  /** This package's version, sent to the hub with every server call. */
29
- export declare const VERSION = "0.34.0";
29
+ export declare const VERSION = "0.45.0";
30
30
  /** Augmented by the generated awesomate.d.ts, so each kind's rows are typed. */
31
31
  export interface Kinds {
32
32
  }
@@ -135,7 +135,7 @@ export interface QueryOptions<T, S extends keyof T = keyof T> {
135
135
  * read, tags included, any case. Narrows like where does, and the two combine.
136
136
  */
137
137
  search?: string;
138
- /** IANA zone for dates and time variables. Default Australia/Sydney. */
138
+ /** IANA zone for dates and time variables. Default: the business's time zone from Your business, Sydney if none is set. */
139
139
  tz?: string;
140
140
  }
141
141
  /** What the hub assumed for a query (order, limit, time zone, what each time variable meant), so it can be said back. */
@@ -243,6 +243,28 @@ export interface TaskCard {
243
243
  canDrop: boolean;
244
244
  canEdit: boolean;
245
245
  } | null;
246
+ /**
247
+ * Set for a task given to a role on the business map rather than a person (an agent's or a
248
+ * stage's): everyone in the role sees it until one of them takes it. `queued` while nobody has;
249
+ * `takenBy` once someone has; `mayTake` when this token's owner may take it with take(). Null or
250
+ * absent for every other card.
251
+ */
252
+ role?: {
253
+ jobId: number;
254
+ slug: string | null;
255
+ title: string | null;
256
+ queued: boolean;
257
+ takenBy: TaskPerson | null;
258
+ /** Nobody took it in 4 business hours, so it went to the person responsible for the role. */
259
+ unclaimed?: boolean;
260
+ /** While it waits: when it goes to the person responsible if nobody takes it, and that as a sentence. */
261
+ goesTo?: {
262
+ at: string;
263
+ words: string;
264
+ } | null;
265
+ inRole: boolean;
266
+ mayTake: boolean;
267
+ } | null;
246
268
  }
247
269
  /** A card's one-tap action, by name (the hub page behind the card runs it). */
248
270
  export type TaskCardAction = {
@@ -258,6 +280,11 @@ export type TaskCardAction = {
258
280
  } | {
259
281
  name: 'finish_task';
260
282
  taskId: number;
283
+ }
284
+ /** A role's task nobody has taken yet: take() makes it yours. */
285
+ | {
286
+ name: 'take_task';
287
+ taskId: number;
261
288
  } | {
262
289
  name: 'approve_public_reply';
263
290
  taskId: number;
@@ -337,6 +364,16 @@ export interface DecisionCard {
337
364
  kind: 'person' | 'owners';
338
365
  label: string;
339
366
  };
367
+ /**
368
+ * A role's decision within the role's limits: anyone in `role` who can decide it may answer it, and
369
+ * the first answer decides it. `people` is how many may answer it now; `line` is the sentence the
370
+ * hub shows ("Any of you in Bookkeeping can answer."). Null or absent when only `decider` can.
371
+ */
372
+ shared?: {
373
+ role: string;
374
+ people: number;
375
+ line: string;
376
+ } | null;
340
377
  /** Work waits behind it: a yes lets it carry on. */
341
378
  holdsWork: boolean;
342
379
  /** What may be pressed now. Always empty for an account token: deciding is a person's tap in the hub. */
@@ -468,6 +505,13 @@ export interface GiveTask {
468
505
  dueAt?: string;
469
506
  /** A hub path the task points at, e.g. /contacts/people/... */
470
507
  linkPath?: string;
508
+ /**
509
+ * Something that can't wait: a hot lead, a fault, a staff alert. It goes to the person's linked
510
+ * chats straight away, past their daily limit, and in their quiet hours only to the chat they chose
511
+ * to be woken in (or not until morning, if they chose none). At most five a day per person; urgent
512
+ * wears off a day after it is given. Leave it out for anything else.
513
+ */
514
+ urgent?: boolean;
471
515
  }
472
516
  /** A file or folder in the account's Files (the folders on its automation account: public/, private/, temp/). */
473
517
  export interface FileEntry {
@@ -722,6 +766,7 @@ export declare class AwesomateClient {
722
766
  *
723
767
  * @example
724
768
  * await db.tasks.give({ title: 'Call Sam back about the leak', forEmail: 'jo@brightwater.example', dueAt: '2026-10-06T09:00:00+11:00' });
769
+ * await db.tasks.give({ title: 'Hot lead: Sam wants a quote today', forEmail: 'jo@brightwater.example', urgent: true });
725
770
  */
726
771
  readonly tasks: {
727
772
  /** The board: waiting now, put off, with someone else, recently decided. `all` shows everyone's, not only yours. hosting:read. */
@@ -745,15 +790,34 @@ export declare class AwesomateClient {
745
790
  decisionRecord: (taskId: number | string) => Promise<TaskRecord & {
746
791
  kind: "decision";
747
792
  }>;
748
- /** Give a task. The person gets it on their board, and an email when they are not the one giving it. hosting:manage. */
793
+ /**
794
+ * Give a task. The person gets it on their board, an email when they are not the one giving it,
795
+ * and a card in any chat they linked, within their quiet hours and daily limit (`urgent` goes
796
+ * straight away). `urgent` in the result says whether it went as urgent: a person's sixth urgent
797
+ * task in a day goes as an ordinary one, and `note` says so. hosting:manage.
798
+ */
749
799
  give: (task: GiveTask) => Promise<{
750
800
  key: string;
751
801
  id: number;
752
802
  holder: TaskPerson;
753
803
  emailed: boolean;
804
+ urgent: boolean;
805
+ note?: string;
754
806
  }>;
755
- /** Change a given task: whoever wrote it, or the owner. hosting:manage. */
756
- update: (id: number | string, patch: Partial<Omit<GiveTask, "forEmail">>) => Promise<void>;
807
+ /**
808
+ * Change a given task: whoever wrote it, or the owner. `urgent: false` takes urgent off; a task can
809
+ * only be marked urgent when it is given. hosting:manage.
810
+ */
811
+ update: (id: number | string, patch: Partial<Omit<GiveTask, "forEmail" | "urgent">> & {
812
+ urgent?: false;
813
+ }) => Promise<void>;
814
+ /**
815
+ * Take a role's task (a card with `role.mayTake`): it becomes the owner's, and everyone else in
816
+ * the role sees who took it. Only someone in the role can take it, so this works for the owner
817
+ * only when they are in that role (serverCode `not_in_role` otherwise). Someone who got there
818
+ * first answers `taken`, with their name in the message. hosting:manage.
819
+ */
820
+ take: (id: number | string) => Promise<void>;
757
821
  /** Mark a given task done: the person who has it, whoever wrote it, or the owner. hosting:manage. */
758
822
  done: (id: number | string) => Promise<void>;
759
823
  /** Take a given task off the board without doing it: whoever wrote it, or the owner. hosting:manage. */
@@ -1022,6 +1086,19 @@ export declare class AwesomateClient {
1022
1086
  addTags: (id: string, tags: string[]) => Promise<ContactTagsResult>;
1023
1087
  /** Take tags off a person, up to 20 a call. Answers how many they had. */
1024
1088
  removeTags: (id: string, tags: string[]) => Promise<ContactTagsResult>;
1089
+ /**
1090
+ * Record that the person asked to stop getting email, by reply, phone, in person or in writing
1091
+ * (`how`), at the time they asked (`askedAt`, default now, at most 30 days back). With no `list`
1092
+ * it stops every email, booking confirmations and other service emails included; with a list's
1093
+ * key, that list only. Recorded as the person's own withdrawal, so a later import never puts
1094
+ * them back. Answers `changed: false` when it was already recorded. The law gives 5 business
1095
+ * days from when they asked. A person with no email address is refused (validation).
1096
+ */
1097
+ unsubscribe: (id: string, options: {
1098
+ how: ContactStopHow;
1099
+ list?: string;
1100
+ askedAt?: Date | string;
1101
+ }) => Promise<ContactUnsubscribeResult>;
1025
1102
  /**
1026
1103
  * Add a contact field. The hub makes its key from the label with a random part and answers it:
1027
1104
  * use that key. Nothing here changes a field that exists. `ai_may_read` decides, once and now,
@@ -1151,6 +1228,18 @@ export declare class AwesomateClient {
1151
1228
  get<K extends KindName>(kind: K, id: string, options?: {
1152
1229
  tz?: string;
1153
1230
  }): Promise<RowOf<K> | null>;
1231
+ /**
1232
+ * What points at one row: for each link from another kind (a job's customer, a memo's job), how
1233
+ * many rows point here and the newest of them (`limit`, default 20, at most 100), read as query()
1234
+ * reads that kind. Null when there is no such row. The account's token, crm:read.
1235
+ *
1236
+ * @example
1237
+ * const related = await db.related('contact', contactId);
1238
+ * for (const r of related ?? []) console.log(r.kind, r.link, r.count);
1239
+ */
1240
+ related(kind: KindName, id: string, options?: {
1241
+ limit?: number;
1242
+ }): Promise<RelatedRows[] | null>;
1154
1243
  /** The app kinds this account has defined. The account's token, crm:read, every plan; never an app key. */
1155
1244
  kinds(): Promise<KindDescription[]>;
1156
1245
  /**
@@ -1210,6 +1299,18 @@ export declare class AwesomateClient {
1210
1299
  write<K extends KindName>(kind: K, data: Partial<RowOf<K>> | Record<string, unknown>, options?: WriteOptions): Promise<string>;
1211
1300
  /** Archive a record: it leaves every read and is kept for a restore. Needs what write() needs. */
1212
1301
  archive<K extends KindName>(kind: K, id: string): Promise<void>;
1302
+ /**
1303
+ * Create or change the record that mirrors one in another system, found by that system's name and
1304
+ * its id there (`ref.system`, lower case like `xero`; `ref.externalId`), so a sync run twice changes
1305
+ * the record instead of making a second one. The same checks as write(). A record with that id that
1306
+ * was archived is refused (`conflict`): a sync never undoes an archive or makes a second copy. Companies
1307
+ * are refused here: use companies.upsert(), which matches by ABN and name. The account's token only
1308
+ * (never an app key), crm:write, Support Plus and above.
1309
+ *
1310
+ * @example
1311
+ * const { id, created } = await db.upsertByRef('job', { system: 'servicem8', externalId: 'JOB-0042' }, { title: 'Possum in the roof' }, { links: { customer: contactId } });
1312
+ */
1313
+ upsertByRef<K extends KindName>(kind: K, ref: RecordRef, data?: Partial<RowOf<K>> | Record<string, unknown>, options?: Omit<WriteOptions, 'id'>): Promise<UpsertByRefResult>;
1213
1314
  /** The saved queries on this account. crm:read, every plan: the account's token or an app key. */
1214
1315
  queries(): Promise<SavedQueryDescription[]>;
1215
1316
  /**
@@ -1420,6 +1521,7 @@ export interface BusinessMapRole {
1420
1521
  headsDepartment: boolean;
1421
1522
  /** Runs its sub-department: the person responsible for it, and for its KPIs. */
1422
1523
  headsSubDepartment: boolean;
1524
+ /** The sub-department's key (see BusinessMapSubDepartment.no): match it there for the name, never show it. */
1423
1525
  subDepartmentNo: number | null;
1424
1526
  reportsTo: {
1425
1527
  id: number;
@@ -1436,17 +1538,91 @@ export interface BusinessMapRole {
1436
1538
  level: BusinessMapLevel;
1437
1539
  exceptions: string | null;
1438
1540
  }>;
1541
+ /**
1542
+ * Its helpers. `look` says what the hub draws: an Awesomate agent's picture URL, a full link to the
1543
+ * hub (null: the hub draws its initials; always null when the account does not have Agents) and
1544
+ * whether it is on (null when that could not be read), an n8n agent's icon, an automation's bolt.
1545
+ */
1439
1546
  helpers: Array<BusinessMapHelper & {
1440
1547
  id: number;
1441
1548
  supervisor: string;
1442
1549
  level: BusinessMapLevel | null;
1443
1550
  exceptions: string | null;
1444
1551
  minutesSavedPerRun: number | null;
1552
+ look: {
1553
+ kind: 'awesomate' | 'n8n' | 'automation' | 'external';
1554
+ picture: string | null;
1555
+ state: 'on' | 'off' | null;
1556
+ };
1445
1557
  }>;
1446
1558
  /** The tag this role's procedures carry in 1Brain, such as 'job:quotes'. The map keeps no procedure text. */
1447
1559
  proceduresTag: string;
1448
1560
  /** Quarterly priorities on this role, every quarter. */
1449
1561
  priorities: BusinessMapPriority[];
1562
+ /** Its responsibilities as processes, in the same order: what writes each down and who does it. */
1563
+ processes: BusinessMapProcess[];
1564
+ /** How many processes it has, how many are written down, how many automated. */
1565
+ progress: {
1566
+ processes: number;
1567
+ written: number;
1568
+ automated: number;
1569
+ };
1570
+ /** Ready to hand over: the owner is still responsible for it and every process is written down or automated. */
1571
+ ready: boolean;
1572
+ /** The one line the map shows for it ("Booking reminders failed twice this week"), or null. */
1573
+ flag: string | null;
1574
+ }
1575
+ /** The one next step the map suggests, with its button's words and what the button does. */
1576
+ export interface BusinessMapNextStep {
1577
+ title: string;
1578
+ detail: string;
1579
+ button: string;
1580
+ action: {
1581
+ kind: 'hand_over';
1582
+ roleId: number;
1583
+ roleSlug: string;
1584
+ membershipId: number | null;
1585
+ } | {
1586
+ kind: 'open_role';
1587
+ roleId: number;
1588
+ roleSlug: string;
1589
+ } | {
1590
+ kind: 'link';
1591
+ path: string;
1592
+ } | {
1593
+ kind: 'add_role';
1594
+ departmentNo: number;
1595
+ } | {
1596
+ kind: 'edit_path';
1597
+ } | {
1598
+ kind: 'place_helpers';
1599
+ };
1600
+ }
1601
+ /**
1602
+ * One of a role's processes (a responsibility). Written down: a 1Brain page on the role's own list of
1603
+ * 1Brain systems is linked to it. Automated: an agent or automation on the role does it, at any level.
1604
+ */
1605
+ export interface BusinessMapProcess {
1606
+ text: string;
1607
+ level: BusinessMapLevel;
1608
+ /** The 1Brain page that writes it down, by its ids and title; null when it is not written down yet. */
1609
+ procedure: {
1610
+ accountId: number;
1611
+ itemId: number;
1612
+ title: string;
1613
+ } | null;
1614
+ /** Who does it: the role's holder, or a helper on the role. */
1615
+ doneBy: {
1616
+ kind: 'holder';
1617
+ name: string | null;
1618
+ } | {
1619
+ kind: 'helper';
1620
+ helperId: number;
1621
+ label: string;
1622
+ helperKind: BusinessMapHelper['kind'];
1623
+ };
1624
+ /** How far whoever does it goes alone, in words: a helper's level, else the role's limits, else "A person". */
1625
+ mayActAlone: string;
1450
1626
  }
1451
1627
  /** A quarterly priority on a role: one of the few things it must move this quarter. */
1452
1628
  export interface BusinessMapPriority {
@@ -1465,6 +1641,8 @@ export interface BusinessMapPathStep {
1465
1641
  position: number;
1466
1642
  label: string;
1467
1643
  departmentNo: 1 | 2 | 3 | 4 | 5 | 6 | 7;
1644
+ /** The department's plain name ("Sales"): what people read. */
1645
+ name: string;
1468
1646
  verb: string;
1469
1647
  /** The role that looks after the step, when the owner named one. */
1470
1648
  role: {
@@ -1480,11 +1658,25 @@ export interface BusinessMapPathStep {
1480
1658
  /** Where this step hands over to the next, in the owner's words. */
1481
1659
  handoffRule: string | null;
1482
1660
  }
1483
- /** A sub-department and who runs it: the holder of the role that heads it, else whoever runs the department (byDefault). */
1661
+ /**
1662
+ * A sub-department and who runs it: the holder of the role that heads it, else whoever runs the
1663
+ * department (byDefault). Each business names, orders and adds its own, so they come in the
1664
+ * business's order and by its names.
1665
+ */
1484
1666
  export interface BusinessMapSubDepartment {
1667
+ /**
1668
+ * A key, never shown to people: 1 to 21 for a standard sub-department, 1001 and up for one the
1669
+ * owner added. A role's subDepartmentNo is this key.
1670
+ */
1485
1671
  no: number;
1672
+ /** The business's name for it: show this. */
1486
1673
  name: string;
1487
- gloss: string;
1674
+ /** The standard name, or null for one the owner added. Differs from name when the owner renamed it. */
1675
+ standardName: string | null;
1676
+ /** The owner added it. */
1677
+ custom: boolean;
1678
+ /** What a standard one covers; null for one the owner added. */
1679
+ gloss: string | null;
1488
1680
  runBy: {
1489
1681
  name: string;
1490
1682
  byDefault: boolean;
@@ -1513,6 +1705,7 @@ export interface BusinessMapDepartment {
1513
1705
  helpers: Array<BusinessMapHelper & {
1514
1706
  placedBy: string;
1515
1707
  }>;
1708
+ /** In the business's order, by its names. */
1516
1709
  subDepartments: BusinessMapSubDepartment[];
1517
1710
  ideas: Array<{
1518
1711
  label: string;
@@ -1527,7 +1720,7 @@ export interface BusinessMapDepartment {
1527
1720
  question: string;
1528
1721
  /** 1Brain Departments whose procedures belong in this department. */
1529
1722
  oneBrainDepartments: string[];
1530
- /** People on the map who hold no role yet. They sit in Form (department 1) for display; empty for every other department. */
1723
+ /** People on the map who hold no role yet. They sit in Team and systems (department 1) for display; empty for every other department. */
1531
1724
  noRoleYet: Array<{
1532
1725
  id: number;
1533
1726
  name: string;
@@ -1591,6 +1784,26 @@ export interface BusinessMap {
1591
1784
  departmentsWithHelpers: number;
1592
1785
  roles: number;
1593
1786
  };
1787
+ /**
1788
+ * How far the business has got: the roles the owner is still responsible for (their own included) of
1789
+ * every role, and the processes written down and automated, each of every process.
1790
+ */
1791
+ progress: {
1792
+ rolesYouHold: {
1793
+ held: number;
1794
+ of: number;
1795
+ };
1796
+ written: {
1797
+ done: number;
1798
+ of: number;
1799
+ };
1800
+ automated: {
1801
+ done: number;
1802
+ of: number;
1803
+ };
1804
+ };
1805
+ /** The one next step: a ready role to hand over first, else the most important gap; null when there is none. */
1806
+ nextStep: BusinessMapNextStep | null;
1594
1807
  /** Sources that could not be read just now: a missing helper may simply not have been read. */
1595
1808
  unavailable: string[];
1596
1809
  }
@@ -1653,7 +1866,12 @@ export interface BusinessMapRoleDetail {
1653
1866
  levelLabel: string;
1654
1867
  exceptions: string | null;
1655
1868
  }>;
1869
+ /** Each responsibility as a process, in the same order. */
1870
+ processes: BusinessMapProcess[];
1871
+ progress: BusinessMapRole['progress'];
1872
+ /** `id` is what a process's `doneBy.helperId` and a proposal's `doneByHelperId` name. */
1656
1873
  helpers: Array<{
1874
+ id: number;
1657
1875
  kind: BusinessMapHelper['kind'];
1658
1876
  ref: string;
1659
1877
  label: string;
@@ -1662,6 +1880,7 @@ export interface BusinessMapRoleDetail {
1662
1880
  levelLabel: string | null;
1663
1881
  exceptions: string | null;
1664
1882
  instructionLine: string;
1883
+ look: BusinessMapRole['helpers'][number]['look'];
1665
1884
  }>;
1666
1885
  }
1667
1886
  /**
@@ -1731,11 +1950,13 @@ export interface BusinessMapDivision {
1731
1950
  helpers: Array<BusinessMapHelper & {
1732
1951
  placedBy: string;
1733
1952
  }>;
1734
- /** The sub-departments. */
1953
+ /** The sub-departments, in the business's order: standardName and custom as on BusinessMapSubDepartment. */
1735
1954
  departments: Array<{
1736
1955
  no: number;
1737
1956
  name: string;
1738
- gloss: string;
1957
+ standardName?: string | null;
1958
+ custom?: boolean;
1959
+ gloss: string | null;
1739
1960
  runBy: {
1740
1961
  name: string;
1741
1962
  byDefault: boolean;
@@ -1930,6 +2151,19 @@ export interface WriteOptions {
1930
2151
  /** Set a link by its key ('<id>'), or end it (null). */
1931
2152
  links?: Record<string, string | null>;
1932
2153
  }
2154
+ /** Which record in another system a record mirrors, for upsertByRef(). */
2155
+ export interface RecordRef {
2156
+ /** That system's name, lower case: letters, digits, `_`, `.` or `-`, at most 40 characters (`xero`, `shopify`). */
2157
+ system: string;
2158
+ /** The record's id in that system, 1 to 200 characters. */
2159
+ externalId: string;
2160
+ }
2161
+ /** What upsertByRef() did. */
2162
+ export interface UpsertByRefResult {
2163
+ id: string;
2164
+ /** True when the record was made; false when the one with that id changed. */
2165
+ created: boolean;
2166
+ }
1933
2167
  /** The type of a saved query's or recipe's parameter. */
1934
2168
  export type ParamType = 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'uuid' | 'text_list' | 'number_list';
1935
2169
  /** One parameter of a saved query or recipe. */
@@ -2101,7 +2335,7 @@ export interface Schema {
2101
2335
  max_limit: number;
2102
2336
  statement_timeout_ms: number;
2103
2337
  };
2104
- /** With the account's token: the time zone dates are read in unless a query says otherwise. */
2338
+ /** With the account's token: the time zone dates are read in unless a query says otherwise (the business's own, Sydney if none is set). */
2105
2339
  default_timezone?: string;
2106
2340
  /** With an app key: whether it can only read or can also write. */
2107
2341
  access?: 'read' | 'write';
@@ -2351,6 +2585,43 @@ export interface ContactTagsResult {
2351
2585
  /** Tags taken off them. */
2352
2586
  removed: number;
2353
2587
  }
2588
+ /** One link pointing at a row, from related(). */
2589
+ export interface RelatedRows {
2590
+ /** The kind the link starts from. */
2591
+ kind: string;
2592
+ /** That kind's name as the owner wrote it, plural ("Jobs"): show this beside the label. */
2593
+ kind_label: string;
2594
+ /** The link's key on that kind (its rows carry <link>_id). A company's people come as kind contact, link company. */
2595
+ link: string;
2596
+ /** What the link is called from this side, as its maker named it ("has" when they did not). */
2597
+ label: string;
2598
+ /** True when this link could not be read just now: count and rows are empty, never zero. */
2599
+ unavailable?: boolean;
2600
+ /** Fields and sections of this kind your role cannot see, when any were left out. */
2601
+ not_shown_to_you?: NotShownToYou;
2602
+ /** How many rows point here, up to 10,000 (count_capped past it). */
2603
+ count: number;
2604
+ count_capped: boolean;
2605
+ /** The newest of them, only columns query() would show. */
2606
+ rows: Array<Record<string, unknown>>;
2607
+ /** Pass to query() with where { <link>_id: id } and after, for more. */
2608
+ next: string | null;
2609
+ }
2610
+ /** How a person asked to stop getting email, for contacts.unsubscribe(). */
2611
+ export type ContactStopHow = 'reply' | 'phone' | 'in_person' | 'in_writing' | 'other';
2612
+ /** What contacts.unsubscribe() recorded. */
2613
+ export interface ContactUnsubscribeResult {
2614
+ id: string;
2615
+ /** False when it was already recorded. */
2616
+ changed: boolean;
2617
+ /** Whether email reaches them now. */
2618
+ email_status: ContactEmailStatus;
2619
+ /** The list they left, or null for every email. */
2620
+ list: string | null;
2621
+ how: ContactStopHow;
2622
+ /** When they asked, ISO 8601. */
2623
+ asked_at: string;
2624
+ }
2354
2625
  /** The kinds of contact field contacts.createField() makes. */
2355
2626
  export type ContactFieldType = 'text' | 'long_text' | 'number' | 'money' | 'date' | 'yes_no' | 'choice' | 'choices';
2356
2627
  /** What contacts.createField() takes. */
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@
26
26
  * Docs: https://hub.awesomate.ai/docs/sdk/
27
27
  */
28
28
  /** This package's version, sent to the hub with every server call. */
29
- export const VERSION = '0.34.0';
29
+ export const VERSION = '0.45.0';
30
30
  const DEFAULT_BASE = 'https://hub.awesomate.ai';
31
31
  /** Every record and contact id is a UUID; anything else can name no row. */
32
32
  const RECORD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -414,6 +414,7 @@ export class AwesomateClient {
414
414
  *
415
415
  * @example
416
416
  * await db.tasks.give({ title: 'Call Sam back about the leak', forEmail: 'jo@brightwater.example', dueAt: '2026-10-06T09:00:00+11:00' });
417
+ * await db.tasks.give({ title: 'Hot lead: Sam wants a quote today', forEmail: 'jo@brightwater.example', urgent: true });
417
418
  */
418
419
  tasks = {
419
420
  /** The board: waiting now, put off, with someone else, recently decided. `all` shows everyone's, not only yours. hosting:read. */
@@ -431,15 +432,32 @@ export class AwesomateClient {
431
432
  * token may not see reads as not found (serverCode `gone`). hosting:read.
432
433
  */
433
434
  decisionRecord: (taskId) => this.tasksCall('GET', `/api/my-tasks/decisions/${encodeURIComponent(String(taskId))}/record`),
434
- /** Give a task. The person gets it on their board, and an email when they are not the one giving it. hosting:manage. */
435
+ /**
436
+ * Give a task. The person gets it on their board, an email when they are not the one giving it,
437
+ * and a card in any chat they linked, within their quiet hours and daily limit (`urgent` goes
438
+ * straight away). `urgent` in the result says whether it went as urgent: a person's sixth urgent
439
+ * task in a day goes as an ordinary one, and `note` says so. hosting:manage.
440
+ */
435
441
  give: async (task) => {
436
442
  const r = await this.tasksCall('POST', '/api/my-tasks/tasks', task);
437
- return { key: r.key, id: r.id, holder: r.holder, emailed: r.emailed };
443
+ return { key: r.key, id: r.id, holder: r.holder, emailed: r.emailed, urgent: r.urgent === true, ...(r.note ? { note: r.note } : {}) };
438
444
  },
439
- /** Change a given task: whoever wrote it, or the owner. hosting:manage. */
445
+ /**
446
+ * Change a given task: whoever wrote it, or the owner. `urgent: false` takes urgent off; a task can
447
+ * only be marked urgent when it is given. hosting:manage.
448
+ */
440
449
  update: async (id, patch) => {
441
450
  await this.tasksCall('PATCH', `/api/my-tasks/tasks/${encodeURIComponent(String(id))}`, patch);
442
451
  },
452
+ /**
453
+ * Take a role's task (a card with `role.mayTake`): it becomes the owner's, and everyone else in
454
+ * the role sees who took it. Only someone in the role can take it, so this works for the owner
455
+ * only when they are in that role (serverCode `not_in_role` otherwise). Someone who got there
456
+ * first answers `taken`, with their name in the message. hosting:manage.
457
+ */
458
+ take: async (id) => {
459
+ await this.tasksCall('POST', `/api/my-tasks/tasks/${encodeURIComponent(String(id))}/take`);
460
+ },
443
461
  /** Mark a given task done: the person who has it, whoever wrote it, or the owner. hosting:manage. */
444
462
  done: async (id) => {
445
463
  await this.tasksCall('POST', `/api/my-tasks/tasks/${encodeURIComponent(String(id))}/done`);
@@ -786,6 +804,20 @@ export class AwesomateClient {
786
804
  addTags: async (id, tags) => this.contactsWrite('POST', `/${contactIdOf(id)}/tags`, { add: tags }),
787
805
  /** Take tags off a person, up to 20 a call. Answers how many they had. */
788
806
  removeTags: async (id, tags) => this.contactsWrite('POST', `/${contactIdOf(id)}/tags`, { remove: tags }),
807
+ /**
808
+ * Record that the person asked to stop getting email, by reply, phone, in person or in writing
809
+ * (`how`), at the time they asked (`askedAt`, default now, at most 30 days back). With no `list`
810
+ * it stops every email, booking confirmations and other service emails included; with a list's
811
+ * key, that list only. Recorded as the person's own withdrawal, so a later import never puts
812
+ * them back. Answers `changed: false` when it was already recorded. The law gives 5 business
813
+ * days from when they asked. A person with no email address is refused (validation).
814
+ */
815
+ unsubscribe: async (id, options) => {
816
+ const askedAt = options.askedAt instanceof Date ? options.askedAt.toISOString() : options.askedAt;
817
+ return this.contactsWrite('POST', `/${contactIdOf(id)}/unsubscribe`, {
818
+ how: options.how, ...(options.list !== undefined ? { list: options.list } : {}), ...(askedAt !== undefined ? { asked_at: askedAt } : {}),
819
+ });
820
+ },
789
821
  /**
790
822
  * Add a contact field. The hub makes its key from the label with a random part and answers it:
791
823
  * use that key. Nothing here changes a field that exists. `ai_may_read` decides, once and now,
@@ -1010,6 +1042,22 @@ export class AwesomateClient {
1010
1042
  throw err;
1011
1043
  }
1012
1044
  }
1045
+ /**
1046
+ * What points at one row: for each link from another kind (a job's customer, a memo's job), how
1047
+ * many rows point here and the newest of them (`limit`, default 20, at most 100), read as query()
1048
+ * reads that kind. Null when there is no such row. The account's token, crm:read.
1049
+ *
1050
+ * @example
1051
+ * const related = await db.related('contact', contactId);
1052
+ * for (const r of related ?? []) console.log(r.kind, r.link, r.count);
1053
+ */
1054
+ async related(kind, id, options = {}) {
1055
+ if (!RECORD_ID.test(String(id)))
1056
+ return null;
1057
+ const qs = options.limit ? `?limit=${options.limit}` : '';
1058
+ const r = await nullIfMissing(this.request('GET', `/api/my-crm/v1/rows/${encodeURIComponent(kind)}/${encodeURIComponent(String(id))}/related${qs}`));
1059
+ return r ? r.related : null;
1060
+ }
1013
1061
  /** The app kinds this account has defined. The account's token, crm:read, every plan; never an app key. */
1014
1062
  async kinds() {
1015
1063
  return (await this.request('GET', '/api/my-crm/v1/kinds')).kinds;
@@ -1087,6 +1135,25 @@ export class AwesomateClient {
1087
1135
  async archive(kind, id) {
1088
1136
  await this.request('POST', '/api/my-crm/v1/call', { fn: 'archive_record', args: { kind, id } }, false);
1089
1137
  }
1138
+ /**
1139
+ * Create or change the record that mirrors one in another system, found by that system's name and
1140
+ * its id there (`ref.system`, lower case like `xero`; `ref.externalId`), so a sync run twice changes
1141
+ * the record instead of making a second one. The same checks as write(). A record with that id that
1142
+ * was archived is refused (`conflict`): a sync never undoes an archive or makes a second copy. Companies
1143
+ * are refused here: use companies.upsert(), which matches by ABN and name. The account's token only
1144
+ * (never an app key), crm:write, Support Plus and above.
1145
+ *
1146
+ * @example
1147
+ * const { id, created } = await db.upsertByRef('job', { system: 'servicem8', externalId: 'JOB-0042' }, { title: 'Possum in the roof' }, { links: { customer: contactId } });
1148
+ */
1149
+ async upsertByRef(kind, ref, data = {}, options = {}) {
1150
+ if (this.appKey)
1151
+ throw new AwesomateError('forbidden', "upsertByRef needs the account's token (amt_pat_...). An app key writes records with write().", 0);
1152
+ const r = await this.request('POST', `/api/my-crm/v1/records/${encodeURIComponent(kind)}/by-ref`, {
1153
+ system: ref.system, external_id: ref.externalId, data, links: options.links ?? {},
1154
+ }, false);
1155
+ return r.result;
1156
+ }
1090
1157
  /** The saved queries on this account. crm:read, every plan: the account's token or an app key. */
1091
1158
  async queries() {
1092
1159
  return (await this.request('GET', '/api/my-crm/v1/queries')).queries;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/sdk",
3
- "version": "0.34.0",
3
+ "version": "0.45.0",
4
4
  "description": "Your own Awesomate data from Node and the browser: query contacts and app data with generated types, and sign your app's own users in",
5
5
  "license": "MIT",
6
6
  "type": "module",