@chatlychat/cli 0.4.0 → 0.5.1

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.
Files changed (83) hide show
  1. package/dist/commands/auth.d.ts +1 -1
  2. package/dist/commands/auth.d.ts.map +1 -1
  3. package/dist/commands/auth.js +40 -3
  4. package/dist/commands/auth.js.map +1 -1
  5. package/dist/commands/init.d.ts +29 -0
  6. package/dist/commands/init.d.ts.map +1 -0
  7. package/dist/commands/init.js +176 -0
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/commands/integrations.d.ts +27 -0
  10. package/dist/commands/integrations.d.ts.map +1 -1
  11. package/dist/commands/integrations.js +133 -3
  12. package/dist/commands/integrations.js.map +1 -1
  13. package/dist/commands/platform-as-code.d.ts +54 -0
  14. package/dist/commands/platform-as-code.d.ts.map +1 -0
  15. package/dist/commands/platform-as-code.js +185 -0
  16. package/dist/commands/platform-as-code.js.map +1 -0
  17. package/dist/commands/workspace-as-code.d.ts +65 -11
  18. package/dist/commands/workspace-as-code.d.ts.map +1 -1
  19. package/dist/commands/workspace-as-code.js +35 -177
  20. package/dist/commands/workspace-as-code.js.map +1 -1
  21. package/dist/help.d.ts +1 -1
  22. package/dist/help.d.ts.map +1 -1
  23. package/dist/help.js +30 -1
  24. package/dist/help.js.map +1 -1
  25. package/dist/init/shared.d.ts +61 -0
  26. package/dist/init/shared.d.ts.map +1 -0
  27. package/dist/init/shared.js +110 -0
  28. package/dist/init/shared.js.map +1 -0
  29. package/dist/init/templates.d.ts +29 -0
  30. package/dist/init/templates.d.ts.map +1 -0
  31. package/dist/init/templates.js +362 -0
  32. package/dist/init/templates.js.map +1 -0
  33. package/dist/pac/platform-document.d.ts +65 -0
  34. package/dist/pac/platform-document.d.ts.map +1 -0
  35. package/dist/pac/platform-document.js +149 -0
  36. package/dist/pac/platform-document.js.map +1 -0
  37. package/dist/pac/platform-live.d.ts +65 -0
  38. package/dist/pac/platform-live.d.ts.map +1 -0
  39. package/dist/pac/platform-live.js +263 -0
  40. package/dist/pac/platform-live.js.map +1 -0
  41. package/dist/pac/platform-resources.d.ts +141 -0
  42. package/dist/pac/platform-resources.d.ts.map +1 -0
  43. package/dist/pac/platform-resources.js +509 -0
  44. package/dist/pac/platform-resources.js.map +1 -0
  45. package/dist/run.d.ts.map +1 -1
  46. package/dist/run.js +23 -2
  47. package/dist/run.js.map +1 -1
  48. package/dist/wac/apply.d.ts.map +1 -1
  49. package/dist/wac/apply.js +52 -15
  50. package/dist/wac/apply.js.map +1 -1
  51. package/dist/wac/diff-warnings.d.ts +35 -0
  52. package/dist/wac/diff-warnings.d.ts.map +1 -0
  53. package/dist/wac/diff-warnings.js +87 -0
  54. package/dist/wac/diff-warnings.js.map +1 -0
  55. package/dist/wac/diff.d.ts +40 -70
  56. package/dist/wac/diff.d.ts.map +1 -1
  57. package/dist/wac/diff.js +90 -91
  58. package/dist/wac/diff.js.map +1 -1
  59. package/dist/wac/document-key.d.ts +30 -0
  60. package/dist/wac/document-key.d.ts.map +1 -0
  61. package/dist/wac/document-key.js +80 -0
  62. package/dist/wac/document-key.js.map +1 -0
  63. package/dist/wac/document.d.ts +75 -65
  64. package/dist/wac/document.d.ts.map +1 -1
  65. package/dist/wac/document.js +60 -46
  66. package/dist/wac/document.js.map +1 -1
  67. package/dist/wac/execute.d.ts +25 -0
  68. package/dist/wac/execute.d.ts.map +1 -0
  69. package/dist/wac/execute.js +126 -0
  70. package/dist/wac/execute.js.map +1 -0
  71. package/dist/wac/load.d.ts +15 -0
  72. package/dist/wac/load.d.ts.map +1 -0
  73. package/dist/wac/load.js +91 -0
  74. package/dist/wac/load.js.map +1 -0
  75. package/dist/wac/resource-def.d.ts +85 -1
  76. package/dist/wac/resource-def.d.ts.map +1 -1
  77. package/dist/wac/resource-def.js +4 -0
  78. package/dist/wac/resource-def.js.map +1 -1
  79. package/dist/wac/values.d.ts +7 -0
  80. package/dist/wac/values.d.ts.map +1 -1
  81. package/dist/wac/values.js +16 -0
  82. package/dist/wac/values.js.map +1 -1
  83. package/package.json +2 -2
package/dist/help.js CHANGED
@@ -31,6 +31,7 @@ AUTH
31
31
  login Store an API key in ~/.config/chatly/config.json (0600)
32
32
  logout Remove the stored key
33
33
  whoami Which workspace this key belongs to, and its scopes
34
+ (a cp_ credential: scopes, expiry and guardrails)
34
35
 
35
36
  CONVERSATIONS
36
37
  conversations list Open conversations, newest first
@@ -96,7 +97,7 @@ ROUTING + AUTOMATION
96
97
  knowledge-gaps list | triage <id>
97
98
 
98
99
  INTEGRATIONS
99
- webhooks deliveries --status
100
+ webhooks deliveries --status --endpoint --trace <ulid> (X-Chatly-Trace)
100
101
  webhooks delivery <id>
101
102
  webhooks redeliver <id>
102
103
  webhooks endpoints Subscriptions, not traffic
@@ -112,6 +113,18 @@ INTEGRATIONS
112
113
  docs-sources subscribe <id> A refusal is a success: read the response
113
114
  channels list | get <id> | adapters | create | update <id>
114
115
  channels rotate-secret <id>
116
+ channels brand <id> --url <brand.json> | --file/--body to apply; neither reads back
117
+
118
+ DATA RIGHTS
119
+ privacy executor-key Mint the credential a privacy platform registers.
120
+ Answers with the executor URL, the workspace id
121
+ and the secret — shown ONCE. The key carries
122
+ exactly privacy:execute and reaches nothing but
123
+ /v1/privacy/*
124
+ privacy lookup --body <json> resolve a person to a subjectId
125
+ privacy request --body <json> file one; caseId is the replay key
126
+ privacy status <id> Read a request back
127
+ privacy cancel <id> Withdraw one that has not run
115
128
 
116
129
  DEVELOPING AGAINST WEBHOOKS AND AGENTS
117
130
  listen Tail this workspace's webhook deliveries and POST
@@ -145,6 +158,22 @@ WORKSPACE AS CODE
145
158
  --prune also delete what the document omits
146
159
  --write-only also send fields that cannot be read back
147
160
  --exit-code plan only: exit 2 when there is drift
161
+ platform plan The same over /v1/platform/* with a cp_ credential:
162
+ platform apply workspaces, users, apiKeys, botCredentials,
163
+ knowledgeSources and settings (business hours,
164
+ routing, widget channel) across many tenants.
165
+ Default file: ./chatly.platform.json. Same flags.
166
+ apply re-reads afterwards and exits 1 unless
167
+ everything matches the document
168
+
169
+ INSTALL
170
+ init next|remix|astro|express
171
+ Scaffold the widget into this project for a web channel:
172
+ identity route, launcher or snippet, CSP sources
173
+ (chatly.csp.mjs) and a privacy table. Never overwrites
174
+ --public-id <id> which web channel (needed if several)
175
+ --identity hmac|atlas default hmac (atlas: next, remix)
176
+ --dir <path> default . --dry-run --force
148
177
 
149
178
  AI
150
179
  ai providers | quota
package/dist/help.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2KnB,CAAC"}
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwMnB,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The parts of `chatly init` (CHT-F02) that do not depend on the framework:
3
+ * the CSP module and the privacy-policy table. Both are rendered from the
4
+ * channel's own `embed` block, read off `GET /v1/channels` with the caller's
5
+ * key — the same block the dashboard's install screen shows — so neither is a
6
+ * hand-kept copy of something the server already says.
7
+ */
8
+ /** One generated file. `path` is relative to the target directory. */
9
+ export interface ScaffoldFile {
10
+ readonly path: string;
11
+ readonly contents: string;
12
+ }
13
+ /** The fields of `channel.embed` this wizard reads. Everything else is ignored. */
14
+ export interface EmbedBlock {
15
+ readonly snippet: string;
16
+ readonly contentSecurityPolicy: {
17
+ readonly scriptSrc: ReadonlyArray<string>;
18
+ readonly connectSrc: ReadonlyArray<string>;
19
+ readonly imgSrc: ReadonlyArray<string>;
20
+ readonly mediaSrc: ReadonlyArray<string>;
21
+ };
22
+ readonly storage: ReadonlyArray<StorageKey>;
23
+ }
24
+ /** One row of `embed.storage` — the public storage declaration. */
25
+ export interface StorageKey {
26
+ readonly key: string;
27
+ readonly store: string;
28
+ readonly purpose: string;
29
+ readonly lifetime: string;
30
+ readonly category: string;
31
+ readonly writtenWhen: string;
32
+ }
33
+ export interface ScaffoldInput {
34
+ readonly publicId: string;
35
+ readonly embed: EmbedBlock;
36
+ /** `hmac` signs the user id on your server; `atlas` passes an Atlas token. */
37
+ readonly identity: 'hmac' | 'atlas';
38
+ /** ISO date, stamped into each header so a stale file can be recognised. */
39
+ readonly generatedOn: string;
40
+ }
41
+ /**
42
+ * The module every framework's CSP is merged from.
43
+ *
44
+ * `.mjs` and dependency-free on purpose: `next.config.{js,mjs,ts}` and an
45
+ * Express server can both import it, and a header built from it needs no
46
+ * library. It does NOT write a header itself — the host page already has a
47
+ * policy, and a second `Content-Security-Policy` header INTERSECTS with the
48
+ * first rather than extending it, which would block the host's own scripts.
49
+ * So it merges into the directives the integrator already has.
50
+ */
51
+ export declare function cspModule(input: ScaffoldInput): ScaffoldFile;
52
+ /**
53
+ * The privacy-policy table (CHT-DOC12's content, rendered for this channel).
54
+ *
55
+ * Every key with the channel's `publicId` already substituted, which is the
56
+ * form a privacy policy or a consent tool's cookie scan needs. `cookies` is
57
+ * stated as none because the declaration says so — an omitted row reads as
58
+ * "not declared", not as "none".
59
+ */
60
+ export declare function privacyTable(input: ScaffoldInput): ScaffoldFile;
61
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/init/shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,sEAAsE;AACtE,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,mFAAmF;AACnF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,qBAAqB,EAAE;QAC9B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAC1C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QAC3C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;QACvC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KAC1C,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;CAC7C;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAwD5D;AAOD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CA0B/D"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The parts of `chatly init` (CHT-F02) that do not depend on the framework:
3
+ * the CSP module and the privacy-policy table. Both are rendered from the
4
+ * channel's own `embed` block, read off `GET /v1/channels` with the caller's
5
+ * key — the same block the dashboard's install screen shows — so neither is a
6
+ * hand-kept copy of something the server already says.
7
+ */
8
+ /**
9
+ * The module every framework's CSP is merged from.
10
+ *
11
+ * `.mjs` and dependency-free on purpose: `next.config.{js,mjs,ts}` and an
12
+ * Express server can both import it, and a header built from it needs no
13
+ * library. It does NOT write a header itself — the host page already has a
14
+ * policy, and a second `Content-Security-Policy` header INTERSECTS with the
15
+ * first rather than extending it, which would block the host's own scripts.
16
+ * So it merges into the directives the integrator already has.
17
+ */
18
+ export function cspModule(input) {
19
+ const csp = input.embed.contentSecurityPolicy;
20
+ const list = (values) => `[${values.map((v) => JSON.stringify(v)).join(', ')}]`;
21
+ return {
22
+ path: 'chatly.csp.mjs',
23
+ contents: `/**
24
+ * Chatly's Content-Security-Policy sources for the web channel ${input.publicId}.
25
+ *
26
+ * Generated by \`chatly init\` on ${input.generatedOn} from the channel's own
27
+ * \`embed.contentSecurityPolicy\`. Re-run \`chatly init\` (or read
28
+ * \`GET /v1/channels\`) when Chatly announces a host change — the lists are
29
+ * the origins CHATLY serves from, not an image URL an operator put in a theme.
30
+ *
31
+ * MERGE these into your existing directives; do not send a second
32
+ * Content-Security-Policy header. Two headers intersect, so a second one
33
+ * would block your own scripts rather than allow Chatly's.
34
+ *
35
+ * The embed snippet is an inline <script>: your script-src needs a nonce or
36
+ * hash for it, or load the widget with @chatlychat/widget-react instead.
37
+ */
38
+ export const chatlyCsp = {
39
+ 'script-src': ${list(csp.scriptSrc)},
40
+ 'connect-src': ${list(csp.connectSrc)},
41
+ 'img-src': ${list(csp.imgSrc)},
42
+ 'media-src': ${list(csp.mediaSrc)},
43
+ };
44
+
45
+ /**
46
+ * Your directives with Chatly's sources appended, de-duplicated.
47
+ * Directive names are the header's own (\`'script-src'\`, not \`scriptSrc\`).
48
+ *
49
+ * @param {Record<string, string[]>} directives
50
+ * @returns {Record<string, string[]>}
51
+ */
52
+ export function withChatly(directives) {
53
+ const out = { ...directives };
54
+ for (const [name, sources] of Object.entries(chatlyCsp)) {
55
+ out[name] = [...new Set([...(out[name] ?? ["'self'"]), ...sources])];
56
+ }
57
+ return out;
58
+ }
59
+
60
+ /**
61
+ * Directives to a header value: \`"script-src 'self' https://…; connect-src …"\`.
62
+ *
63
+ * @param {Record<string, string[]>} directives
64
+ * @returns {string}
65
+ */
66
+ export function toHeader(directives) {
67
+ return Object.entries(directives)
68
+ .map(([name, sources]) => [name, ...sources].join(' '))
69
+ .join('; ');
70
+ }
71
+ `,
72
+ };
73
+ }
74
+ /** Escape a value for a Markdown table cell. */
75
+ function cell(value) {
76
+ return value.replaceAll('|', '\\|').replaceAll('\n', ' ');
77
+ }
78
+ /**
79
+ * The privacy-policy table (CHT-DOC12's content, rendered for this channel).
80
+ *
81
+ * Every key with the channel's `publicId` already substituted, which is the
82
+ * form a privacy policy or a consent tool's cookie scan needs. `cookies` is
83
+ * stated as none because the declaration says so — an omitted row reads as
84
+ * "not declared", not as "none".
85
+ */
86
+ export function privacyTable(input) {
87
+ const rows = input.embed.storage.map((k) => `| \`${cell(k.key)}\` | ${cell(k.store)} | ${cell(k.purpose)} | ${cell(k.lifetime)} | ` +
88
+ `${cell(k.category)} | ${cell(k.writtenWhen)} |`);
89
+ return {
90
+ path: 'CHATLY-PRIVACY.md',
91
+ contents: `# What the Chatly widget stores in a visitor's browser
92
+
93
+ Generated by \`chatly init\` on ${input.generatedOn} from the storage
94
+ declaration of web channel \`${input.publicId}\` — the same list Chatly
95
+ publishes for that channel without a key. Paste the table into your privacy
96
+ policy or cookie notice.
97
+
98
+ **Cookies: none.** The widget sets no cookies.
99
+
100
+ | Key | Store | Purpose | Lifetime | Category | Written when |
101
+ | --- | --- | --- | --- | --- | --- |
102
+ ${rows.join('\n')}
103
+
104
+ Whether ANY of these is written is decided by the \`storage\` option on your
105
+ embed, which the server never sees — \`storage: 'deny'\` writes none of them.
106
+ See https://chatlychat.com/guides/consent.
107
+ `,
108
+ };
109
+ }
110
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/init/shared.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAuCH;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,KAAoB;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC;IAC9C,MAAM,IAAI,GAAG,CAAC,MAA6B,EAAU,EAAE,CACrD,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;IACzD,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE;kEACoD,KAAK,CAAC,QAAQ;;qCAE3C,KAAK,CAAC,WAAW;;;;;;;;;;;;;kBAapC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;mBAClB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;eACxB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;iBACd,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BlC;KACE,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAoB;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAClC,CAAC,CAAC,EAAE,EAAE,CACJ,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK;QACvF,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CACnD,CAAC;IACF,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE;;kCAEoB,KAAK,CAAC,WAAW;+BACpB,KAAK,CAAC,QAAQ;;;;;;;;EAQ3C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;CAKhB;KACE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The framework templates for `chatly init` (CHT-F02).
3
+ *
4
+ * Every API a generated file uses is in a PUBLISHED package at the version on
5
+ * npm today — `chatlyUserHash` from `@chatlychat/widget-react/server`,
6
+ * `<ChatlyLauncher>`'s `externalId`/`userHash`/`getToken` props, and
7
+ * `Chatly('identify', …)` on the snippet. A template that imported something
8
+ * from a working tree would scaffold a project that does not install.
9
+ *
10
+ * ═══ THE ONE THING A WIZARD CANNOT KNOW ═══════════════════════════════════
11
+ *
12
+ * Who is signed in. Every identity route below has a `currentUserId` stub
13
+ * that answers `null`, so a freshly scaffolded app runs with every visitor
14
+ * ANONYMOUS — never with an unsigned claim. Wiring it to the integrator's
15
+ * auth is the step the summary names first.
16
+ */
17
+ import { type ScaffoldFile, type ScaffoldInput } from './shared.js';
18
+ export declare const FRAMEWORKS: readonly ["next", "remix", "astro", "express"];
19
+ export type Framework = (typeof FRAMEWORKS)[number];
20
+ /**
21
+ * The frameworks with no `--identity atlas` output. An Atlas token is minted
22
+ * in the browser, so there is no server half to generate, and these two have
23
+ * no React component to pass `getToken` to — the page calls
24
+ * `Chatly('identify', { getToken })` itself.
25
+ */
26
+ export declare const NO_ATLAS: ReadonlyArray<Framework>;
27
+ /** Every file `chatly init <framework>` writes, in the order it reports them. */
28
+ export declare function scaffold(framework: Framework, input: ScaffoldInput): ReadonlyArray<ScaffoldFile>;
29
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/init/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAA2B,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7F,eAAO,MAAM,UAAU,gDAAiD,CAAC;AACzE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAwB,CAAC;AAsTvE,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,GAAG,aAAa,CAAC,YAAY,CAAC,CAwChG"}
@@ -0,0 +1,362 @@
1
+ /**
2
+ * The framework templates for `chatly init` (CHT-F02).
3
+ *
4
+ * Every API a generated file uses is in a PUBLISHED package at the version on
5
+ * npm today — `chatlyUserHash` from `@chatlychat/widget-react/server`,
6
+ * `<ChatlyLauncher>`'s `externalId`/`userHash`/`getToken` props, and
7
+ * `Chatly('identify', …)` on the snippet. A template that imported something
8
+ * from a working tree would scaffold a project that does not install.
9
+ *
10
+ * ═══ THE ONE THING A WIZARD CANNOT KNOW ═══════════════════════════════════
11
+ *
12
+ * Who is signed in. Every identity route below has a `currentUserId` stub
13
+ * that answers `null`, so a freshly scaffolded app runs with every visitor
14
+ * ANONYMOUS — never with an unsigned claim. Wiring it to the integrator's
15
+ * auth is the step the summary names first.
16
+ */
17
+ import { cspModule, privacyTable } from './shared.js';
18
+ export const FRAMEWORKS = ['next', 'remix', 'astro', 'express'];
19
+ /**
20
+ * The frameworks with no `--identity atlas` output. An Atlas token is minted
21
+ * in the browser, so there is no server half to generate, and these two have
22
+ * no React component to pass `getToken` to — the page calls
23
+ * `Chatly('identify', { getToken })` itself.
24
+ */
25
+ export const NO_ATLAS = ['express', 'astro'];
26
+ const IDENTITY_ROUTE_NEXT = (input) => `/**
27
+ * Chatly identity verification (HMAC) for web channel ${input.publicId}.
28
+ * Generated by \`chatly init next\` on ${input.generatedOn}.
29
+ *
30
+ * Signs the signed-in user's id with the channel's IDV secret, so the widget
31
+ * can prove who the visitor is. The secret stays on this server:
32
+ * \`@chatlychat/widget-react/server\` refuses to run in a browser bundle.
33
+ *
34
+ * Wire \`currentUserId\` to your auth before shipping. Until then it answers
35
+ * \`{ identity: null }\` and every visitor chats anonymously.
36
+ */
37
+ import { chatlyUserHash } from '@chatlychat/widget-react/server';
38
+ import { NextResponse } from 'next/server';
39
+
40
+ export const dynamic = 'force-dynamic';
41
+
42
+ const NO_STORE = { 'cache-control': 'no-store' };
43
+
44
+ /** TODO: return the signed-in user's stable id from your auth library, or null. */
45
+ async function currentUserId(): Promise<string | null> {
46
+ return null;
47
+ }
48
+
49
+ export async function GET(): Promise<NextResponse> {
50
+ const externalId = await currentUserId();
51
+ if (externalId === null) {
52
+ return NextResponse.json({ identity: null }, { headers: NO_STORE });
53
+ }
54
+ const secret = process.env.CHATLY_IDV_SECRET;
55
+ if (secret === undefined || secret === '') {
56
+ // Loud rather than anonymous: a signed-in user silently chatting as a
57
+ // stranger is a misconfiguration nobody would notice.
58
+ return NextResponse.json(
59
+ { error: 'CHATLY_IDV_SECRET is not set on the server' },
60
+ { status: 500, headers: NO_STORE },
61
+ );
62
+ }
63
+ return NextResponse.json(
64
+ { identity: { externalId, userHash: chatlyUserHash(secret, externalId) } },
65
+ { headers: NO_STORE },
66
+ );
67
+ }
68
+ `;
69
+ const LAUNCHER_NEXT_HMAC = (input) => `'use client';
70
+ /**
71
+ * The Chatly launcher for web channel ${input.publicId}, identified through
72
+ * \`app/api/chatly/identity/route.ts\`. Generated by \`chatly init next\` on
73
+ * ${input.generatedOn}. Render <Chatly /> once, in your root layout.
74
+ */
75
+ import { ChatlyLauncher } from '@chatlychat/widget-react';
76
+ import { useEffect, useState } from 'react';
77
+
78
+ interface Identity {
79
+ readonly externalId: string;
80
+ readonly userHash: string;
81
+ }
82
+
83
+ export function Chatly() {
84
+ // undefined = still asking; null = anonymous.
85
+ const [identity, setIdentity] = useState<Identity | null | undefined>(undefined);
86
+
87
+ useEffect(() => {
88
+ let live = true;
89
+ fetch('/api/chatly/identity', { cache: 'no-store' })
90
+ .then(async (r) => (r.ok ? ((await r.json()) as { identity: Identity | null }) : { identity: null }))
91
+ .then((body) => live && setIdentity(body.identity))
92
+ .catch(() => live && setIdentity(null));
93
+ return () => {
94
+ live = false;
95
+ };
96
+ }, []);
97
+
98
+ // Rendered only once the identity is known, so the widget starts as the
99
+ // right visitor instead of starting anonymous and switching.
100
+ if (identity === undefined) {
101
+ return null;
102
+ }
103
+ return <ChatlyLauncher publicId=${JSON.stringify(input.publicId)} {...(identity ?? {})} />;
104
+ }
105
+ `;
106
+ const LAUNCHER_NEXT_ATLAS = (input) => `'use client';
107
+ /**
108
+ * The Chatly launcher for web channel ${input.publicId}, identified with an
109
+ * Atlas token (\`identityVerification.mode: 'atlas'\`). Generated by
110
+ * \`chatly init next --identity atlas\` on ${input.generatedOn}.
111
+ *
112
+ * Pass \`getToken\` for a signed-in user and leave it out for a signed-out one:
113
+ *
114
+ * <Chatly getToken={() => atlas.session.getToken({ template: 'chatly' })} />
115
+ *
116
+ * The template and the channel setting it must match are on
117
+ * https://chatlychat.com/guides/identity-verification#worked-example-an-atlas-instance-on-the-shared-host
118
+ */
119
+ import { ChatlyLauncher } from '@chatlychat/widget-react';
120
+
121
+ export function Chatly({ getToken }: { getToken?: () => Promise<string> }) {
122
+ return (
123
+ <ChatlyLauncher
124
+ publicId=${JSON.stringify(input.publicId)}
125
+ {...(getToken === undefined ? {} : { getToken })}
126
+ />
127
+ );
128
+ }
129
+ `;
130
+ const IDENTITY_EXPRESS = (input) => `/**
131
+ * Chatly identity verification (HMAC) for web channel ${input.publicId}.
132
+ * Generated by \`chatly init express\` on ${input.generatedOn}.
133
+ *
134
+ * import { chatlyIdentity } from './chatly/identity.mjs';
135
+ * app.get('/api/chatly/identity', chatlyIdentity((req) => req.user?.id ?? null));
136
+ *
137
+ * \`userIdOf\` is the one thing to write: return the signed-in user's stable id,
138
+ * or null for a signed-out visitor, who then chats anonymously.
139
+ */
140
+ import { createHmac } from 'node:crypto';
141
+
142
+ /** HMAC-SHA256(secret, externalId), hex — what the widget sends as userHash. */
143
+ export function chatlyUserHash(secret, externalId) {
144
+ return createHmac('sha256', secret).update(externalId).digest('hex');
145
+ }
146
+
147
+ /** @param {(req: any) => string | null | Promise<string | null>} userIdOf */
148
+ export function chatlyIdentity(userIdOf) {
149
+ return async (req, res) => {
150
+ res.set('cache-control', 'no-store');
151
+ const externalId = await userIdOf(req);
152
+ if (externalId === null || externalId === undefined) {
153
+ return res.json({ identity: null });
154
+ }
155
+ const secret = process.env.CHATLY_IDV_SECRET;
156
+ if (!secret) {
157
+ return res.status(500).json({ error: 'CHATLY_IDV_SECRET is not set on the server' });
158
+ }
159
+ return res.json({ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } });
160
+ };
161
+ }
162
+ `;
163
+ const SNIPPET_EXPRESS = (input) => `<!--
164
+ Chatly for web channel ${input.publicId}. Generated by \`chatly init express\`
165
+ on ${input.generatedOn}. Paste before </body>. The first script is the
166
+ channel's own embed snippet; the second identifies a signed-in visitor
167
+ through /api/chatly/identity (see chatly/identity.mjs).
168
+ -->
169
+ ${input.embed.snippet}
170
+ <script>
171
+ fetch('/api/chatly/identity', { cache: 'no-store' })
172
+ .then(function (r) { return r.ok ? r.json() : { identity: null }; })
173
+ .then(function (body) { if (body.identity) Chatly('identify', body.identity); })
174
+ .catch(function () {});
175
+ </script>
176
+ `;
177
+ /**
178
+ * The Remix loader imports nothing from Remix or React Router on purpose. A
179
+ * `LoaderFunctionArgs` import would pin the file to `@remix-run/node` (Remix
180
+ * v2) or to `react-router` (v7 framework mode), and a loader that takes
181
+ * `{ request }` and returns a `Response` is a valid loader in both.
182
+ */
183
+ const IDENTITY_ROUTE_REMIX = (input) => `/**
184
+ * Chatly identity verification (HMAC) for web channel ${input.publicId}.
185
+ * Generated by \`chatly init remix\` on ${input.generatedOn}.
186
+ *
187
+ * A resource route (a loader and no component), served at
188
+ * /api/chatly/identity by Remix v2's flat routes and by React Router v7's
189
+ * \`flatRoutes()\`. Signs the signed-in user's id with the channel's IDV
190
+ * secret, so the widget can prove who the visitor is. The secret stays on
191
+ * this server: a loader never reaches the browser bundle, and
192
+ * \`@chatlychat/widget-react/server\` refuses to run in one anyway.
193
+ *
194
+ * Wire \`currentUserId\` to your auth before shipping. Until then it answers
195
+ * \`{ identity: null }\` and every visitor chats anonymously.
196
+ */
197
+ import { chatlyUserHash } from '@chatlychat/widget-react/server';
198
+
199
+ const HEADERS = { 'content-type': 'application/json', 'cache-control': 'no-store' };
200
+
201
+ /** TODO: return the signed-in user's stable id from your session, or null. */
202
+ async function currentUserId(request: Request): Promise<string | null> {
203
+ void request;
204
+ return null;
205
+ }
206
+
207
+ export async function loader({ request }: { request: Request }): Promise<Response> {
208
+ const externalId = await currentUserId(request);
209
+ if (externalId === null) {
210
+ return new Response(JSON.stringify({ identity: null }), { headers: HEADERS });
211
+ }
212
+ const secret = process.env.CHATLY_IDV_SECRET;
213
+ if (secret === undefined || secret === '') {
214
+ // Loud rather than anonymous: a signed-in user silently chatting as a
215
+ // stranger is a misconfiguration nobody would notice.
216
+ return new Response(JSON.stringify({ error: 'CHATLY_IDV_SECRET is not set on the server' }), {
217
+ status: 500,
218
+ headers: HEADERS,
219
+ });
220
+ }
221
+ return new Response(
222
+ JSON.stringify({ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } }),
223
+ { headers: HEADERS },
224
+ );
225
+ }
226
+ `;
227
+ /**
228
+ * Remix's launcher is Next's without `'use client'` and with its own header:
229
+ * every Remix component renders on the server and hydrates, and the identity
230
+ * fetch is in an effect, so it runs in the browser only.
231
+ */
232
+ const LAUNCHER_REMIX_HMAC = (input) => LAUNCHER_NEXT_HMAC(input)
233
+ .replace(/^'use client';\n/, '')
234
+ .replace('`app/api/chatly/identity/route.ts`', '`app/routes/api.chatly.identity.ts`')
235
+ .replace('`chatly init next`', '`chatly init remix`')
236
+ .replace('in your root layout', 'in app/root.tsx');
237
+ const LAUNCHER_REMIX_ATLAS = (input) => LAUNCHER_NEXT_ATLAS(input)
238
+ .replace(/^'use client';\n/, '')
239
+ .replace('`chatly init next --identity atlas`', '`chatly init remix --identity atlas`');
240
+ /**
241
+ * Astro's endpoint signs with `node:crypto`, like Express's, rather than
242
+ * importing `@chatlychat/widget-react/server`: an Astro site has no reason to
243
+ * install a React package, and the construction is one line.
244
+ */
245
+ const IDENTITY_ROUTE_ASTRO = (input) => `/**
246
+ * Chatly identity verification (HMAC) for web channel ${input.publicId}.
247
+ * Generated by \`chatly init astro\` on ${input.generatedOn}.
248
+ *
249
+ * Served at /api/chatly/identity. \`prerender = false\` makes it run per
250
+ * request, which needs a server adapter with Node built-ins
251
+ * (\`@astrojs/node\`, or another adapter with \`node:crypto\`): a prerendered
252
+ * identity would be one visitor's answer frozen into a static file.
253
+ *
254
+ * Wire \`currentUserId\` to your auth before shipping. Until then it answers
255
+ * \`{ identity: null }\` and every visitor chats anonymously.
256
+ */
257
+ import { createHmac } from 'node:crypto';
258
+
259
+ import type { APIRoute } from 'astro';
260
+
261
+ export const prerender = false;
262
+
263
+ const HEADERS = { 'content-type': 'application/json', 'cache-control': 'no-store' };
264
+
265
+ /** HMAC-SHA256(secret, externalId), hex — what the widget sends as userHash. */
266
+ export function chatlyUserHash(secret: string, externalId: string): string {
267
+ return createHmac('sha256', secret).update(externalId).digest('hex');
268
+ }
269
+
270
+ /** TODO: return the signed-in user's stable id (from \`locals\`, a cookie, …), or null. */
271
+ async function currentUserId(context: Parameters<APIRoute>[0]): Promise<string | null> {
272
+ void context;
273
+ return null;
274
+ }
275
+
276
+ export const GET: APIRoute = async (context) => {
277
+ const externalId = await currentUserId(context);
278
+ if (externalId === null) {
279
+ return new Response(JSON.stringify({ identity: null }), { headers: HEADERS });
280
+ }
281
+ const secret = process.env.CHATLY_IDV_SECRET;
282
+ if (secret === undefined || secret === '') {
283
+ // Loud rather than anonymous: a signed-in user silently chatting as a
284
+ // stranger is a misconfiguration nobody would notice.
285
+ return new Response(JSON.stringify({ error: 'CHATLY_IDV_SECRET is not set on the server' }), {
286
+ status: 500,
287
+ headers: HEADERS,
288
+ });
289
+ }
290
+ return new Response(
291
+ JSON.stringify({ identity: { externalId, userHash: chatlyUserHash(secret, externalId) } }),
292
+ { headers: HEADERS },
293
+ );
294
+ };
295
+ `;
296
+ /**
297
+ * The snippet is a string in the frontmatter and rendered with `set:html`, not
298
+ * pasted into the template: a bare `<script>` in an `.astro` file is bundled
299
+ * and turned into a module, which would move the shim off `window` and out of
300
+ * order. `set:html` emits it byte for byte, and `is:inline` does the same for
301
+ * the identify call.
302
+ */
303
+ const COMPONENT_ASTRO = (input) => `---
304
+ /**
305
+ * Chatly for web channel ${input.publicId}. Generated by \`chatly init astro\`
306
+ * on ${input.generatedOn}. Render <Chatly /> once, before </body> in your
307
+ * layout. The first script is the channel's own embed snippet; the second
308
+ * identifies a signed-in visitor through /api/chatly/identity
309
+ * (src/pages/api/chatly/identity.ts).
310
+ */
311
+ const snippet = ${JSON.stringify(input.embed.snippet)};
312
+ ---
313
+
314
+ <Fragment set:html={snippet} />
315
+ <script is:inline>
316
+ fetch('/api/chatly/identity', { cache: 'no-store' })
317
+ .then(function (r) { return r.ok ? r.json() : { identity: null }; })
318
+ .then(function (body) { if (body.identity) Chatly('identify', body.identity); })
319
+ .catch(function () {});
320
+ </script>
321
+ `;
322
+ /** Every file `chatly init <framework>` writes, in the order it reports them. */
323
+ export function scaffold(framework, input) {
324
+ const common = [cspModule(input), privacyTable(input)];
325
+ if (framework === 'next') {
326
+ return [
327
+ ...(input.identity === 'hmac'
328
+ ? [{ path: 'app/api/chatly/identity/route.ts', contents: IDENTITY_ROUTE_NEXT(input) }]
329
+ : []),
330
+ {
331
+ path: 'components/chatly.tsx',
332
+ contents: input.identity === 'hmac' ? LAUNCHER_NEXT_HMAC(input) : LAUNCHER_NEXT_ATLAS(input),
333
+ },
334
+ ...common,
335
+ ];
336
+ }
337
+ if (framework === 'remix') {
338
+ return [
339
+ ...(input.identity === 'hmac'
340
+ ? [{ path: 'app/routes/api.chatly.identity.ts', contents: IDENTITY_ROUTE_REMIX(input) }]
341
+ : []),
342
+ {
343
+ path: 'app/components/chatly.tsx',
344
+ contents: input.identity === 'hmac' ? LAUNCHER_REMIX_HMAC(input) : LAUNCHER_REMIX_ATLAS(input),
345
+ },
346
+ ...common,
347
+ ];
348
+ }
349
+ if (framework === 'astro') {
350
+ return [
351
+ { path: 'src/pages/api/chatly/identity.ts', contents: IDENTITY_ROUTE_ASTRO(input) },
352
+ { path: 'src/components/Chatly.astro', contents: COMPONENT_ASTRO(input) },
353
+ ...common,
354
+ ];
355
+ }
356
+ return [
357
+ { path: 'chatly/identity.mjs', contents: IDENTITY_EXPRESS(input) },
358
+ { path: 'chatly/snippet.html', contents: SNIPPET_EXPRESS(input) },
359
+ ...common,
360
+ ];
361
+ }
362
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/init/templates.ts"],"names":[],"mappings":"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"}