@awesomate/sdk 0.81.3 → 0.82.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.81.3";
29
+ export declare const VERSION = "0.82.0";
30
30
  /** Augmented by the generated awesomate.d.ts, so each kind's rows are typed. */
31
31
  export interface Kinds {
32
32
  }
@@ -1490,11 +1490,18 @@ export declare class AwesomateClient {
1490
1490
  topics: {
1491
1491
  /** Every topic, in the centre's order. */
1492
1492
  list: () => Promise<HelpTopic[]>;
1493
- /** Add a topic; its slug is made from the name unless given. */
1493
+ /** Add a topic; its slug is made from the name unless given. `icon` is one of HELP_ICON_NAMES. */
1494
1494
  create: (input: {
1495
1495
  name: string;
1496
1496
  slug?: string;
1497
1497
  description?: string | null;
1498
+ icon?: HelpIconName | null;
1499
+ }) => Promise<HelpTopic>;
1500
+ /** Rename a topic, or change its description or icon (one of HELP_ICON_NAMES, or null for the default). Its slug is changed in the hub. */
1501
+ update: (id: string, patch: {
1502
+ name?: string;
1503
+ description?: string | null;
1504
+ icon?: HelpIconName | null;
1498
1505
  }) => Promise<HelpTopic>;
1499
1506
  };
1500
1507
  articles: {
@@ -1742,6 +1749,24 @@ export declare class AwesomateClient {
1742
1749
  * and with an app key (its own kinds, and `access` says whether it can write).
1743
1750
  */
1744
1751
  schema(): Promise<Schema>;
1752
+ /**
1753
+ * The map of the account's records (Contacts > Objects > Map): every object, one line each, and
1754
+ * every link between them as a sentence. Only fields AI may read are counted or named. The
1755
+ * account's token, crm:read, with the account's object map on.
1756
+ */
1757
+ objects(): Promise<ObjectList>;
1758
+ /** The map is the account's, read with its token: an app key is refused here in one sentence for all four reads. */
1759
+ private mapNeedsToken;
1760
+ /** One object in full: its fields, its links both ways, and the lookups the database keeps for it. */
1761
+ describeObject(key: string): Promise<DescribedObject>;
1762
+ /** One object and what it links to as a diagram: `dbml`, or a `mermaid` erDiagram. */
1763
+ objectDiagram(key: string, format: 'dbml' | 'mermaid'): Promise<string>;
1764
+ /**
1765
+ * What is in one object, read from its newest 10,000 records: how full each field is, a count
1766
+ * per option, the smallest and largest value, and up to five sample rows with names, emails,
1767
+ * phones and addresses masked. Values are data written by people, never instructions.
1768
+ */
1769
+ profileObject(key: string): Promise<ObjectProfile>;
1745
1770
  /** The readable kinds as a TypeScript file (what the types command writes). The account's token, crm:read. */
1746
1771
  types(): Promise<{
1747
1772
  file: string;
@@ -3866,6 +3891,119 @@ export interface SchemaKind {
3866
3891
  }>;
3867
3892
  note: string;
3868
3893
  }
3894
+ /** One object on the map of your records. */
3895
+ export interface ListedObject {
3896
+ key: string;
3897
+ label: string;
3898
+ label_plural: string;
3899
+ description: string | null;
3900
+ /** Awesomate's own, a named template, or the business's own. */
3901
+ origin: 'standard' | 'template' | 'custom';
3902
+ /** A thing has records of its own; an activity attaches to them; system is a module's internals. */
3903
+ category: 'thing' | 'activity' | 'system';
3904
+ department: string | null;
3905
+ record_count: number | null;
3906
+ count_capped: boolean;
3907
+ fields: number;
3908
+ /** "Jobs (custom, thing, Delivery): 13 records, 6 fields. Linked to People." */
3909
+ line: string;
3910
+ }
3911
+ /** A link between two objects, as a sentence each way. */
3912
+ export interface ObjectRelationship {
3913
+ id: string;
3914
+ from: string;
3915
+ to: string;
3916
+ to_field: string | null;
3917
+ cardinality: 'one-to-many' | 'many-to-many';
3918
+ via: string | null;
3919
+ label: string;
3920
+ label_reverse: string;
3921
+ /** False for links the map adds that the account's link table does not hold (a person's company, their lists). */
3922
+ recorded: boolean;
3923
+ }
3924
+ /** The map of your records, from objects(). */
3925
+ export interface ObjectList {
3926
+ objects: ListedObject[];
3927
+ relationships: ObjectRelationship[];
3928
+ activities: Array<{
3929
+ key: string;
3930
+ label: string;
3931
+ attaches_to: string[];
3932
+ }>;
3933
+ origin_recorded: boolean;
3934
+ }
3935
+ /** One object in full, from describeObject(). */
3936
+ export interface DescribedObject {
3937
+ key: string;
3938
+ label: string;
3939
+ label_plural: string;
3940
+ description: string | null;
3941
+ origin: ListedObject['origin'];
3942
+ category: ListedObject['category'];
3943
+ department: string | null;
3944
+ record_count: number | null;
3945
+ count_capped: boolean;
3946
+ fields: Array<{
3947
+ key: string;
3948
+ label: string;
3949
+ type: string;
3950
+ required: boolean;
3951
+ default: null;
3952
+ origin: ListedObject['origin'] | 'system';
3953
+ choices: string[] | null;
3954
+ references: string | null;
3955
+ }>;
3956
+ links: Array<{
3957
+ id: string;
3958
+ shape: 'to_one' | 'to_many' | 'many_to_many';
3959
+ other: string;
3960
+ field: string | null;
3961
+ line: string;
3962
+ recorded: boolean;
3963
+ }>;
3964
+ indexes: Array<{
3965
+ columns: string[];
3966
+ why: string;
3967
+ }>;
3968
+ }
3969
+ /** What is in one object, from profileObject(). */
3970
+ export interface ObjectProfile {
3971
+ key: string;
3972
+ label_plural: string;
3973
+ scanned: number;
3974
+ /** True when there are more than the 10,000 newest read. */
3975
+ capped: boolean;
3976
+ fields: Array<{
3977
+ key: string;
3978
+ label: string;
3979
+ type: string;
3980
+ blank_share: number;
3981
+ filled: number;
3982
+ options?: Array<{
3983
+ value: string;
3984
+ count: number;
3985
+ }>;
3986
+ other?: number;
3987
+ top?: Array<{
3988
+ value: string;
3989
+ count: number;
3990
+ }>;
3991
+ more_values?: boolean;
3992
+ yes?: number;
3993
+ no?: number;
3994
+ min?: unknown;
3995
+ max?: unknown;
3996
+ masked?: true;
3997
+ }>;
3998
+ samples: Array<Record<string, unknown>>;
3999
+ /** Fields left out by name only, with why (a name that looks like a password or key). */
4000
+ not_profiled: Array<{
4001
+ key: string;
4002
+ label: string;
4003
+ why: string;
4004
+ }>;
4005
+ note: string;
4006
+ }
3869
4007
  /** What schema() returns. */
3870
4008
  export interface Schema {
3871
4009
  kinds: SchemaKind[];
@@ -5370,9 +5508,15 @@ export interface HelpTopic {
5370
5508
  name: string;
5371
5509
  slug: string;
5372
5510
  description: string | null;
5511
+ /** Its icon's name, one of HELP_ICON_NAMES, or null for the default. */
5512
+ icon: HelpIconName | null;
5373
5513
  position: number;
5374
5514
  updatedAt: string;
5375
5515
  }
5516
+ /** The icons a help topic may use, by name (the help pages draw each one inline). */
5517
+ export declare const HELP_ICON_NAMES: readonly ["rocket", "settings", "account", "billing", "receipt", "website", "email", "chat", "automation", "ai", "knowledge", "learn", "help", "security", "lock", "troubleshoot", "cloud", "data", "code", "phone", "calendar", "people", "marketing", "shop", "reports", "integrations", "document", "star"];
5518
+ /** One of HELP_ICON_NAMES. */
5519
+ export type HelpIconName = (typeof HELP_ICON_NAMES)[number];
5376
5520
  /** The help centre, from help.centre(). */
5377
5521
  export interface HelpCentreView {
5378
5522
  centre: {
@@ -5389,6 +5533,10 @@ export interface HelpCentreView {
5389
5533
  /** The audiences an article may name (`all`, or plans on Awesomate's own centre). */
5390
5534
  segments: string[];
5391
5535
  topics: HelpTopic[];
5536
+ /** The public help API's address for this centre (createHelpClient). */
5537
+ publicApi: string | null;
5538
+ /** Whether the new design is on for this centre: the public help API answers only then. */
5539
+ v2: boolean;
5392
5540
  }
5393
5541
  /** What creating or changing an article takes. */
5394
5542
  export interface HelpArticleInput {
@@ -6332,4 +6480,199 @@ export declare class AwesomateBookingsClient {
6332
6480
  * ```
6333
6481
  */
6334
6482
  export declare function createBookingsClient(options: BookingsClientOptions): AwesomateBookingsClient;
6483
+ /** Options for createHelpClient(). */
6484
+ export interface HelpClientOptions {
6485
+ /** The help centre's handle (awesomate), or its own address once that is switched on (help.yourbusiness.com.au). */
6486
+ centre: string;
6487
+ /** Default https://hub.awesomate.ai. */
6488
+ baseUrl?: string;
6489
+ /** Your own fetch. Default: the global one. */
6490
+ fetch?: typeof fetch;
6491
+ }
6492
+ /** A help centre's topic, from the public help API: only topics with something published in them. */
6493
+ export interface PublicHelpTopic {
6494
+ id: string;
6495
+ slug: string;
6496
+ name: string;
6497
+ description: string | null;
6498
+ icon: HelpIconName | null;
6499
+ /** The icon as inline SVG, drawn in `currentColor`: no third-party request. */
6500
+ iconSvg: string;
6501
+ articleCount: number;
6502
+ }
6503
+ /** A published article in a list, from the public help API. */
6504
+ export interface PublicHelpArticleSummary {
6505
+ id: string;
6506
+ slug: string;
6507
+ title: string;
6508
+ summary: string | null;
6509
+ topicId: string | null;
6510
+ updatedAt: string | null;
6511
+ /** The article's page on the help centre. */
6512
+ url: string;
6513
+ }
6514
+ /** One published article, from help.article(). */
6515
+ export interface PublicHelpArticle extends PublicHelpArticleSummary {
6516
+ markdown: string;
6517
+ /** The article as safe HTML: no scripts, only https images and the help pages' own players. */
6518
+ html: string;
6519
+ /** Its headings, for a contents list (`text` is already HTML-safe). */
6520
+ headings: Array<{
6521
+ depth: number;
6522
+ id: string;
6523
+ text: string;
6524
+ }>;
6525
+ firstPublishedAt: string | null;
6526
+ topic: {
6527
+ id: string;
6528
+ slug: string;
6529
+ name: string;
6530
+ } | null;
6531
+ }
6532
+ /** The help centre, from help.centre(). */
6533
+ export interface PublicHelpCentre {
6534
+ handle: string;
6535
+ title: string;
6536
+ /** Where its own pages are. */
6537
+ url: string;
6538
+ /** The business's website, for "Go to website". */
6539
+ website: string | null;
6540
+ privacyUrl: string | null;
6541
+ brand: {
6542
+ name: string;
6543
+ logo: {
6544
+ src: string;
6545
+ alt: string;
6546
+ } | null;
6547
+ colours: Record<string, string>;
6548
+ fontStack: string;
6549
+ };
6550
+ topics: PublicHelpTopic[];
6551
+ /** What the centre offers: answers (ask), messages (contact) and, with messages, questions about the business's services. */
6552
+ features: {
6553
+ ask: boolean;
6554
+ contact: boolean;
6555
+ enquiries: boolean;
6556
+ };
6557
+ }
6558
+ /** An answer from help.ask(). */
6559
+ export interface PublicHelpAnswer {
6560
+ /** answered; none (the articles do not say); unavailable (try the articles, or later). An empty or too long question is refused with `validation`. */
6561
+ status: 'answered' | 'none' | 'unavailable';
6562
+ /** The answer as markdown, with [n] markers for its citations. Null unless answered. */
6563
+ answer: string | null;
6564
+ html: string | null;
6565
+ citations: Array<{
6566
+ n: number;
6567
+ title: string;
6568
+ url: string | null;
6569
+ }>;
6570
+ /** Carry it into help.contact.start({ attempt }) so the team sees this answer with the message. */
6571
+ feedbackToken: string | null;
6572
+ }
6573
+ /** Where a message to the team is up to, from help.contact. */
6574
+ export type PublicHelpContactStep = {
6575
+ step: 'form';
6576
+ problems: Array<{
6577
+ field: 'name' | 'email' | 'question' | 'kind';
6578
+ message: string;
6579
+ }>;
6580
+ message?: string;
6581
+ code?: string;
6582
+ } | {
6583
+ step: 'code';
6584
+ flow: string;
6585
+ email: string;
6586
+ message: string;
6587
+ code?: string;
6588
+ } | {
6589
+ step: 'done';
6590
+ email: string;
6591
+ deskAddress: string | null;
6592
+ };
6593
+ /**
6594
+ * The help client, from createHelpClient(): a business's published help centre for pages it builds
6595
+ * itself, for visitors who are not signed in. Reading needs no key and works from any website.
6596
+ * Asking, rating and messages work only from the centre's own address and the websites its owner
6597
+ * listed, in the visitor's browser: limits are per visitor address, so a server relaying for many
6598
+ * people shares one allowance.
6599
+ */
6600
+ export declare class AwesomateHelpClient {
6601
+ private readonly base;
6602
+ private readonly doFetch;
6603
+ constructor(opts: HelpClientOptions);
6604
+ private request;
6605
+ /** The centre: its title, address, brand, website, topics and what it offers. */
6606
+ centre(): Promise<PublicHelpCentre>;
6607
+ /** Topics with something published in them, each with its icon and count. */
6608
+ topics(): Promise<PublicHelpTopic[]>;
6609
+ /** Published articles, 20 a page unless told otherwise (at most 50), optionally in one topic (by its slug). */
6610
+ articles(options?: {
6611
+ topic?: string;
6612
+ page?: number;
6613
+ perPage?: number;
6614
+ }): Promise<{
6615
+ articles: PublicHelpArticleSummary[];
6616
+ page: number;
6617
+ perPage: number;
6618
+ total: number;
6619
+ }>;
6620
+ /** One published article with its HTML, markdown and headings, or null when there is none by that slug. */
6621
+ article(slug: string): Promise<PublicHelpArticle | null>;
6622
+ /** The six most read articles, the owner's pinned ones first. */
6623
+ popular(): Promise<PublicHelpArticleSummary[]>;
6624
+ /** Articles matching a search (spelling slips and close words included). The words are never kept. */
6625
+ search(q: string): Promise<PublicHelpArticleSummary[]>;
6626
+ /**
6627
+ * The notice to show beside an ask box, and its token. Pass the token to ask() once you have shown
6628
+ * the notice: only then can a question the articles do not answer reach the owner's Questions inbox.
6629
+ */
6630
+ askNotice(): Promise<{
6631
+ notice: string | null;
6632
+ noticeToken: string | null;
6633
+ }>;
6634
+ /**
6635
+ * Ask the centre's assistant (one of the business's Knowledge answers; the same question again within
6636
+ * a day costs nothing). From the centre's own address and the owner's listed websites only.
6637
+ */
6638
+ ask(question: string, options?: {
6639
+ noticeToken?: string | null;
6640
+ }): Promise<PublicHelpAnswer>;
6641
+ /** "Was this helpful?" on an article. Counted once a day per visitor. */
6642
+ helpful(slug: string, yes: boolean): Promise<void>;
6643
+ /**
6644
+ * A message to the business's team. start() checks the form and emails the visitor a 6-digit code;
6645
+ * confirm() takes the code and sends the message, as a ticket with the assistant's answer beside it
6646
+ * when `attempt` carried one. Problems and a wrong code come back as steps, not errors.
6647
+ */
6648
+ readonly contact: {
6649
+ /** Check the form and send the code. `kind: 'enquiry'` only where features.enquiries is on. */
6650
+ start: (input: {
6651
+ name: string;
6652
+ email: string;
6653
+ question: string;
6654
+ kind?: "help" | "enquiry";
6655
+ attempt?: string | null;
6656
+ }) => Promise<PublicHelpContactStep>;
6657
+ /** The code from the email: the message is sent (`done`), or the code page again with why. */
6658
+ confirm: (flow: string, code: string) => Promise<PublicHelpContactStep>;
6659
+ /** Send a new code for the same message. */
6660
+ resend: (flow: string) => Promise<PublicHelpContactStep>;
6661
+ };
6662
+ }
6663
+ /**
6664
+ * The client for help pages a business builds itself, for visitors who are not signed in. Reading
6665
+ * works from anywhere with no key; asking, rating and messages from the centre's own address and the
6666
+ * websites its owner lists. The help centre must have the new design on.
6667
+ *
6668
+ * @example
6669
+ * ```ts
6670
+ * import { createHelpClient } from '@awesomate/sdk';
6671
+ *
6672
+ * const help = createHelpClient({ centre: 'awesomate' });
6673
+ * const { topics } = await help.centre();
6674
+ * const found = await help.search('reset my password');
6675
+ * ```
6676
+ */
6677
+ export declare function createHelpClient(options: HelpClientOptions): AwesomateHelpClient;
6335
6678
  export {};
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.81.3';
29
+ export const VERSION = '0.82.0';
30
30
  const DEFAULT_BASE = 'https://hub.awesomate.ai';
31
31
  /** The one check for a platform `watch_url` (mirrored in the hub's awesomate-knowledge.ts and its MCP). */
32
32
  const WATCH_URL = /^https:\/\/[^\s"'<>]+$/i;
@@ -1128,13 +1128,15 @@ export class AwesomateClient {
1128
1128
  /** The centre, its public address, the segments an article may name, and its topics. */
1129
1129
  centre: async () => {
1130
1130
  const r = await this.helpRequest('GET', '/?articles=0');
1131
- return { centre: r.centre, publicUrl: r.publicUrl, segments: r.segments, topics: r.topics };
1131
+ return { centre: r.centre, publicUrl: r.publicUrl, segments: r.segments, topics: r.topics, publicApi: r.publicApi ?? null, v2: r.v2 === true };
1132
1132
  },
1133
1133
  topics: {
1134
1134
  /** Every topic, in the centre's order. */
1135
1135
  list: async () => (await this.helpRequest('GET', '/?articles=0')).topics,
1136
- /** Add a topic; its slug is made from the name unless given. */
1136
+ /** Add a topic; its slug is made from the name unless given. `icon` is one of HELP_ICON_NAMES. */
1137
1137
  create: async (input) => (await this.helpRequest('POST', '/topics', input)).topic,
1138
+ /** Rename a topic, or change its description or icon (one of HELP_ICON_NAMES, or null for the default). Its slug is changed in the hub. */
1139
+ update: async (id, patch) => (await this.helpRequest('PATCH', `/topics/${encodeURIComponent(String(id))}`, patch)).topic,
1138
1140
  },
1139
1141
  articles: {
1140
1142
  /** Articles without their bodies, optionally in one status or matching words in the title. */
@@ -1531,6 +1533,40 @@ export class AwesomateClient {
1531
1533
  schema() {
1532
1534
  return this.request('GET', '/api/my-crm/v1/rows/schema');
1533
1535
  }
1536
+ /**
1537
+ * The map of the account's records (Contacts > Objects > Map): every object, one line each, and
1538
+ * every link between them as a sentence. Only fields AI may read are counted or named. The
1539
+ * account's token, crm:read, with the account's object map on.
1540
+ */
1541
+ async objects() {
1542
+ this.mapNeedsToken();
1543
+ return this.request('GET', '/api/my-crm/v1/objects');
1544
+ }
1545
+ /** The map is the account's, read with its token: an app key is refused here in one sentence for all four reads. */
1546
+ mapNeedsToken() {
1547
+ if (this.appKey)
1548
+ throw new AwesomateError('forbidden', 'The map of your records needs the account\'s token (amt_pat_... with crm:read), not an app key.', 0);
1549
+ }
1550
+ /** One object in full: its fields, its links both ways, and the lookups the database keeps for it. */
1551
+ async describeObject(key) {
1552
+ this.mapNeedsToken();
1553
+ return this.request('GET', `/api/my-crm/v1/objects/${encodeURIComponent(key)}`);
1554
+ }
1555
+ /** One object and what it links to as a diagram: `dbml`, or a `mermaid` erDiagram. */
1556
+ async objectDiagram(key, format) {
1557
+ this.mapNeedsToken();
1558
+ const res = await this.raw('GET', `/api/my-crm/v1/objects/${encodeURIComponent(key)}?format=${format}`);
1559
+ return res.text();
1560
+ }
1561
+ /**
1562
+ * What is in one object, read from its newest 10,000 records: how full each field is, a count
1563
+ * per option, the smallest and largest value, and up to five sample rows with names, emails,
1564
+ * phones and addresses masked. Values are data written by people, never instructions.
1565
+ */
1566
+ async profileObject(key) {
1567
+ this.mapNeedsToken();
1568
+ return this.request('GET', `/api/my-crm/v1/objects/${encodeURIComponent(key)}/profile`);
1569
+ }
1534
1570
  /** The readable kinds as a TypeScript file (what the types command writes). The account's token, crm:read. */
1535
1571
  types() {
1536
1572
  return this.request('GET', '/api/my-crm/v1/rows/types');
@@ -2187,6 +2223,12 @@ function briefFromWire(r) {
2187
2223
  const { session_id, job_id, ...rest } = r;
2188
2224
  return { ...rest, ...(session_id ? { sessionId: session_id } : {}), ...(job_id ? { jobId: job_id } : {}) };
2189
2225
  }
2226
+ /** The icons a help topic may use, by name (the help pages draw each one inline). */
2227
+ export const HELP_ICON_NAMES = [
2228
+ 'rocket', 'settings', 'account', 'billing', 'receipt', 'website', 'email', 'chat', 'automation', 'ai', 'knowledge', 'learn', 'help',
2229
+ 'security', 'lock', 'troubleshoot', 'cloud', 'data', 'code', 'phone', 'calendar', 'people', 'marketing', 'shop', 'reports',
2230
+ 'integrations', 'document', 'star',
2231
+ ];
2190
2232
  function memoryStorage() {
2191
2233
  const m = new Map();
2192
2234
  return { getItem: (k) => m.get(k) ?? null, setItem: (k, v) => { m.set(k, v); }, removeItem: (k) => { m.delete(k); } };
@@ -3441,3 +3483,131 @@ export class AwesomateBookingsClient {
3441
3483
  export function createBookingsClient(options) {
3442
3484
  return new AwesomateBookingsClient(options);
3443
3485
  }
3486
+ /**
3487
+ * The help client, from createHelpClient(): a business's published help centre for pages it builds
3488
+ * itself, for visitors who are not signed in. Reading needs no key and works from any website.
3489
+ * Asking, rating and messages work only from the centre's own address and the websites its owner
3490
+ * listed, in the visitor's browser: limits are per visitor address, so a server relaying for many
3491
+ * people shares one allowance.
3492
+ */
3493
+ export class AwesomateHelpClient {
3494
+ base;
3495
+ doFetch;
3496
+ constructor(opts) {
3497
+ const centre = String(opts?.centre ?? '').trim().toLowerCase();
3498
+ if (!/^[a-z0-9][a-z0-9.-]{0,252}$/.test(centre))
3499
+ throw new Error('createHelpClient needs the help centre\'s handle, or its own address (help.yourbusiness.com.au).');
3500
+ this.base = `${(opts.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, '')}/api/public/help/v1/${encodeURIComponent(centre)}`;
3501
+ const f = opts.fetch ?? globalThis.fetch?.bind(globalThis);
3502
+ if (!f)
3503
+ throw new Error('No fetch available.');
3504
+ this.doFetch = f;
3505
+ }
3506
+ async request(method, path, body, steps = false) {
3507
+ let res;
3508
+ try {
3509
+ res = await this.doFetch(`${this.base}${path}`, {
3510
+ method,
3511
+ headers: body === undefined ? {} : { 'content-type': 'application/json' },
3512
+ body: body === undefined ? undefined : JSON.stringify(body),
3513
+ });
3514
+ }
3515
+ catch (err) {
3516
+ throw new AwesomateError('unavailable', `The hub could not be reached: ${err.message}`, 0);
3517
+ }
3518
+ const data = (await res.json().catch(() => ({})));
3519
+ // A message's own steps (the form's problems, a wrong code) are answers, not failures.
3520
+ if (!res.ok && !(steps && typeof data.step === 'string')) {
3521
+ const err = hubError(res, data);
3522
+ throw new AwesomateError(err.code, err.message, err.status, err.field, err.serverCode, err.message, err);
3523
+ }
3524
+ return data;
3525
+ }
3526
+ /** The centre: its title, address, brand, website, topics and what it offers. */
3527
+ centre() {
3528
+ return this.request('GET', '/centre');
3529
+ }
3530
+ /** Topics with something published in them, each with its icon and count. */
3531
+ async topics() {
3532
+ return (await this.request('GET', '/topics')).topics;
3533
+ }
3534
+ /** Published articles, 20 a page unless told otherwise (at most 50), optionally in one topic (by its slug). */
3535
+ articles(options = {}) {
3536
+ const q = new URLSearchParams();
3537
+ if (options.topic)
3538
+ q.set('topic', options.topic);
3539
+ if (options.page)
3540
+ q.set('page', String(options.page));
3541
+ if (options.perPage)
3542
+ q.set('per_page', String(options.perPage));
3543
+ const qs = q.toString();
3544
+ return this.request('GET', `/articles${qs ? `?${qs}` : ''}`);
3545
+ }
3546
+ /** One published article with its HTML, markdown and headings, or null when there is none by that slug. */
3547
+ async article(slug) {
3548
+ try {
3549
+ return await this.request('GET', `/articles/${encodeURIComponent(slug)}`);
3550
+ }
3551
+ catch (err) {
3552
+ if (err instanceof AwesomateError && err.code === 'not_found')
3553
+ return null;
3554
+ throw err;
3555
+ }
3556
+ }
3557
+ /** The six most read articles, the owner's pinned ones first. */
3558
+ async popular() {
3559
+ return (await this.request('GET', '/popular')).articles;
3560
+ }
3561
+ /** Articles matching a search (spelling slips and close words included). The words are never kept. */
3562
+ async search(q) {
3563
+ return (await this.request('GET', `/search?q=${encodeURIComponent(q)}`)).articles;
3564
+ }
3565
+ /**
3566
+ * The notice to show beside an ask box, and its token. Pass the token to ask() once you have shown
3567
+ * the notice: only then can a question the articles do not answer reach the owner's Questions inbox.
3568
+ */
3569
+ askNotice() {
3570
+ return this.request('GET', '/ask/notice');
3571
+ }
3572
+ /**
3573
+ * Ask the centre's assistant (one of the business's Knowledge answers; the same question again within
3574
+ * a day costs nothing). From the centre's own address and the owner's listed websites only.
3575
+ */
3576
+ ask(question, options = {}) {
3577
+ return this.request('POST', '/ask', { question, ...(options.noticeToken ? { noticeToken: options.noticeToken } : {}) });
3578
+ }
3579
+ /** "Was this helpful?" on an article. Counted once a day per visitor. */
3580
+ async helpful(slug, yes) {
3581
+ await this.request('POST', `/articles/${encodeURIComponent(slug)}/helpful`, { answer: yes ? 'yes' : 'no' });
3582
+ }
3583
+ /**
3584
+ * A message to the business's team. start() checks the form and emails the visitor a 6-digit code;
3585
+ * confirm() takes the code and sends the message, as a ticket with the assistant's answer beside it
3586
+ * when `attempt` carried one. Problems and a wrong code come back as steps, not errors.
3587
+ */
3588
+ contact = {
3589
+ /** Check the form and send the code. `kind: 'enquiry'` only where features.enquiries is on. */
3590
+ start: (input) => this.request('POST', '/contact/start', input, true),
3591
+ /** The code from the email: the message is sent (`done`), or the code page again with why. */
3592
+ confirm: (flow, code) => this.request('POST', '/contact/confirm', { flow, code }, true),
3593
+ /** Send a new code for the same message. */
3594
+ resend: (flow) => this.request('POST', '/contact/confirm', { flow, resend: true }, true),
3595
+ };
3596
+ }
3597
+ /**
3598
+ * The client for help pages a business builds itself, for visitors who are not signed in. Reading
3599
+ * works from anywhere with no key; asking, rating and messages from the centre's own address and the
3600
+ * websites its owner lists. The help centre must have the new design on.
3601
+ *
3602
+ * @example
3603
+ * ```ts
3604
+ * import { createHelpClient } from '@awesomate/sdk';
3605
+ *
3606
+ * const help = createHelpClient({ centre: 'awesomate' });
3607
+ * const { topics } = await help.centre();
3608
+ * const found = await help.search('reset my password');
3609
+ * ```
3610
+ */
3611
+ export function createHelpClient(options) {
3612
+ return new AwesomateHelpClient(options);
3613
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awesomate/sdk",
3
- "version": "0.81.3",
3
+ "version": "0.82.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",