@chatlychat/cli 0.1.4 → 0.1.6

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 (73) hide show
  1. package/dist/commands/ai.d.ts +4 -0
  2. package/dist/commands/ai.d.ts.map +1 -0
  3. package/dist/commands/ai.js +149 -0
  4. package/dist/commands/ai.js.map +1 -0
  5. package/dist/commands/common.d.ts +79 -0
  6. package/dist/commands/common.d.ts.map +1 -0
  7. package/dist/commands/common.js +194 -0
  8. package/dist/commands/common.js.map +1 -0
  9. package/dist/commands/config.d.ts +40 -0
  10. package/dist/commands/config.d.ts.map +1 -0
  11. package/dist/commands/config.js +336 -0
  12. package/dist/commands/config.js.map +1 -0
  13. package/dist/commands/insights.d.ts +5 -0
  14. package/dist/commands/insights.d.ts.map +1 -0
  15. package/dist/commands/insights.js +277 -0
  16. package/dist/commands/insights.js.map +1 -0
  17. package/dist/commands/integrations.d.ts +15 -0
  18. package/dist/commands/integrations.d.ts.map +1 -0
  19. package/dist/commands/integrations.js +256 -0
  20. package/dist/commands/integrations.js.map +1 -0
  21. package/dist/commands/resources.d.ts +19 -7
  22. package/dist/commands/resources.d.ts.map +1 -1
  23. package/dist/commands/resources.js +92 -167
  24. package/dist/commands/resources.js.map +1 -1
  25. package/dist/commands/workspace-as-code.d.ts +27 -0
  26. package/dist/commands/workspace-as-code.d.ts.map +1 -0
  27. package/dist/commands/workspace-as-code.js +377 -0
  28. package/dist/commands/workspace-as-code.js.map +1 -0
  29. package/dist/help.d.ts +25 -1
  30. package/dist/help.d.ts.map +1 -1
  31. package/dist/help.js +104 -8
  32. package/dist/help.js.map +1 -1
  33. package/dist/run.d.ts +17 -0
  34. package/dist/run.d.ts.map +1 -1
  35. package/dist/run.js +88 -41
  36. package/dist/run.js.map +1 -1
  37. package/dist/wac/__tests__/wac-fixtures.d.ts +20 -0
  38. package/dist/wac/__tests__/wac-fixtures.d.ts.map +1 -0
  39. package/dist/wac/__tests__/wac-fixtures.js +38 -0
  40. package/dist/wac/__tests__/wac-fixtures.js.map +1 -0
  41. package/dist/wac/apply.d.ts +113 -0
  42. package/dist/wac/apply.d.ts.map +1 -0
  43. package/dist/wac/apply.js +265 -0
  44. package/dist/wac/apply.js.map +1 -0
  45. package/dist/wac/diff.d.ts +137 -0
  46. package/dist/wac/diff.d.ts.map +1 -0
  47. package/dist/wac/diff.js +413 -0
  48. package/dist/wac/diff.js.map +1 -0
  49. package/dist/wac/document.d.ts +110 -0
  50. package/dist/wac/document.d.ts.map +1 -0
  51. package/dist/wac/document.js +429 -0
  52. package/dist/wac/document.js.map +1 -0
  53. package/dist/wac/render.d.ts +52 -0
  54. package/dist/wac/render.d.ts.map +1 -0
  55. package/dist/wac/render.js +156 -0
  56. package/dist/wac/render.js.map +1 -0
  57. package/dist/wac/resource-def.d.ts +116 -0
  58. package/dist/wac/resource-def.d.ts.map +1 -0
  59. package/dist/wac/resource-def.js +44 -0
  60. package/dist/wac/resource-def.js.map +1 -0
  61. package/dist/wac/resources.d.ts +104 -0
  62. package/dist/wac/resources.d.ts.map +1 -0
  63. package/dist/wac/resources.js +541 -0
  64. package/dist/wac/resources.js.map +1 -0
  65. package/dist/wac/values.d.ts +16 -0
  66. package/dist/wac/values.d.ts.map +1 -0
  67. package/dist/wac/values.js +53 -0
  68. package/dist/wac/values.js.map +1 -0
  69. package/package.json +2 -1
  70. package/dist/cli.spec.d.ts +0 -2
  71. package/dist/cli.spec.d.ts.map +0 -1
  72. package/dist/cli.spec.js +0 -226
  73. package/dist/cli.spec.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Printing a plan, and one rule behind every choice here: a diff nobody reads
3
+ * is worse than no diff, because it gets approved.
4
+ *
5
+ * So the shape is terraform's, because that is the shape people already know
6
+ * how to read — a sigil per action, the resource addressed as `type/key`, and
7
+ * the changed fields indented under it with old on the left. What is NOT
8
+ * terraform's: unchanged resources are one line each rather than a collapsed
9
+ * count, because on this surface a workspace has tens of resources and not
10
+ * thousands, and "the thing I expected to be unchanged is listed as
11
+ * unchanged" is the second most useful sentence a plan can say.
12
+ *
13
+ * Values are JSON, truncated. A macro body is 20,000 characters and a
14
+ * trigger's `actions` array is a nested object; neither belongs on a terminal
15
+ * line in full, and `--json` exists for the whole thing.
16
+ */
17
+ import { tally } from './diff.js';
18
+ const SIGIL = {
19
+ create: '+',
20
+ update: '~',
21
+ noop: '=',
22
+ orphan: '-',
23
+ blocked: '!',
24
+ };
25
+ const MAX_VALUE = 72;
26
+ /** One value, as JSON, clipped. `undefined` is `(unset)`, not `undefined`. */
27
+ export function show(value) {
28
+ if (value === undefined) {
29
+ return '(unset)';
30
+ }
31
+ const text = JSON.stringify(value) ?? String(value);
32
+ return text.length > MAX_VALUE ? `${text.slice(0, MAX_VALUE - 1)}…` : text;
33
+ }
34
+ function changeLine(item, change) {
35
+ const masked = item.row?.fromEnv.includes(change.name) === true;
36
+ const to = masked ? '(from the environment)' : show(change.to);
37
+ if (item.action === 'create') {
38
+ return ` ${change.name} = ${to}`;
39
+ }
40
+ return ` ${change.name}: ${masked ? '(unreadable)' : show(change.from)} -> ${to}`;
41
+ }
42
+ /** The whole plan, as `chatly plan` prints it. */
43
+ export function renderPlan(plan, opts) {
44
+ const counts = tally(plan);
45
+ const out = [];
46
+ for (const item of plan.items) {
47
+ const head = ` ${SIGIL[item.action]} ${item.type}/${item.key}`;
48
+ if (item.action === 'noop') {
49
+ out.push(`${head} unchanged`);
50
+ for (const note of item.notes) {
51
+ out.push(` · ${note}`);
52
+ }
53
+ continue;
54
+ }
55
+ if (item.action === 'orphan') {
56
+ const fate = item.notes.length > 0
57
+ ? 'kept'
58
+ : opts.pruneUnsafe.includes(item.type)
59
+ ? 'kept — this type cannot be pruned safely on this run'
60
+ : opts.prune
61
+ ? 'DELETE'
62
+ : 'kept — `--prune` would delete it';
63
+ out.push(`${head} on the server, not in this document; ${fate}`);
64
+ for (const note of item.notes) {
65
+ out.push(` · ${note}`);
66
+ }
67
+ continue;
68
+ }
69
+ if (item.action === 'blocked') {
70
+ out.push(`${head} CANNOT APPLY`);
71
+ out.push(` ${item.reason ?? 'no reason recorded, which is itself a bug.'}`);
72
+ for (const change of item.changes) {
73
+ out.push(changeLine(item, change));
74
+ }
75
+ continue;
76
+ }
77
+ out.push(`${head} ${item.action === 'create' ? 'create' : 'change'}`);
78
+ for (const change of item.changes) {
79
+ out.push(changeLine(item, change));
80
+ }
81
+ for (const note of item.notes) {
82
+ out.push(` · ${note}`);
83
+ }
84
+ }
85
+ if (out.length === 0) {
86
+ out.push(' (the document declares no resources)');
87
+ }
88
+ const summary = `${String(counts.create)} to create, ${String(counts.update)} to change, ` +
89
+ `${String(counts.noop)} unchanged, ${String(counts.orphan)} on the server only, ` +
90
+ `${String(counts.blocked)} that cannot be applied`;
91
+ const parts = [`Plan: ${summary}`, '', ...out];
92
+ if (plan.warnings.length > 0) {
93
+ parts.push('', 'Warnings');
94
+ for (const warning of plan.warnings) {
95
+ parts.push(` ! ${warning}`);
96
+ }
97
+ }
98
+ return `${parts.join('\n')}\n`;
99
+ }
100
+ /**
101
+ * The line that follows a plan, and it is load-bearing.
102
+ *
103
+ * `plan` is the half of this feature that is safe, and saying so explicitly
104
+ * is what makes somebody willing to run it against production. "Nothing was
105
+ * written" is a claim the spec holds this CLI to: a recording transport
106
+ * asserts zero non-GET requests over the whole plan path.
107
+ */
108
+ export function planFooter(plan, opts) {
109
+ const counts = tally(plan);
110
+ const lines = ['', 'Nothing was written — `plan` issues reads only.'];
111
+ if (counts.create + counts.update === 0 && (counts.orphan === 0 || !opts.prune)) {
112
+ lines.push('There is nothing to apply.');
113
+ }
114
+ else {
115
+ lines.push(`Run \`chatly apply\`${opts.prune ? ' --prune' : ''} to make ` +
116
+ `${String(counts.create + counts.update + (opts.prune ? counts.orphan : 0))} change(s).`);
117
+ }
118
+ if (counts.blocked > 0) {
119
+ lines.push(`${String(counts.blocked)} resource(s) cannot be applied and \`apply\` will skip them ` +
120
+ 'rather than half-write them. The reason is on each line.');
121
+ }
122
+ return `${lines.join('\n')}\n`;
123
+ }
124
+ /**
125
+ * The plan, for `--json`, with every env-sourced value taken back out.
126
+ *
127
+ * `plan --json | tee plan.json` is a thing somebody does on the first day,
128
+ * and a plan item carries `row.body` — which holds the RESOLVED value of
129
+ * every `${env:…}` reference, including a docs source's provider token. The
130
+ * human renderer already masks those (`fromEnv` on each row); the JSON
131
+ * renderer printed them, which would have written a live credential into a
132
+ * file somebody then commits.
133
+ *
134
+ * So `row` does not go out at all — a consumer wants the diff, not a copy of
135
+ * its own input — and any change whose field came from the environment is
136
+ * reported as having come from the environment rather than by value.
137
+ */
138
+ export function planForJson(plan) {
139
+ return {
140
+ plan: plan.items.map((item) => ({
141
+ type: item.type,
142
+ key: item.key,
143
+ action: item.action,
144
+ liveId: item.liveId ?? null,
145
+ reason: item.reason ?? null,
146
+ notes: item.notes,
147
+ dependsOn: item.dependsOn,
148
+ changes: item.changes.map((change) => item.row?.fromEnv.includes(change.name) === true
149
+ ? { name: change.name, from: '(unreadable)', to: '(from the environment)' }
150
+ : { name: change.name, from: change.from ?? null, to: change.to }),
151
+ })),
152
+ warnings: plan.warnings,
153
+ pruneUnsafe: plan.pruneUnsafe,
154
+ };
155
+ }
156
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/wac/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The SHAPE of a resource definition, and the helpers that read one.
3
+ *
4
+ * Split out of `resources.ts` only because the two together ran past the
5
+ * 650-line ceiling (`scripts/check-file-size.mjs`). The division is the
6
+ * useful one anyway: this file is the vocabulary, `resources.ts` is the nine
7
+ * answers written in it, and reading one does not require reading the other.
8
+ * `resources.ts` re-exports everything here, so every importer sees one
9
+ * module.
10
+ */
11
+ /** The verbs a document can cause. */
12
+ export type Action = 'create' | 'update' | 'noop' | 'orphan' | 'blocked';
13
+ export interface ResourceRef {
14
+ /** The resource type the value names. */
15
+ readonly type: string;
16
+ /** The field the API actually reads, which the ref resolves into. */
17
+ readonly wire: string;
18
+ }
19
+ export interface UpdateRoute {
20
+ readonly method: 'PATCH' | 'PUT';
21
+ /** `{id}` is replaced with the encoded live id. */
22
+ readonly path: string;
23
+ /**
24
+ * The ONLY fields this route can change. Omitted means "anything not
25
+ * claimed by a more specific route" — the catch-all. A resource with no
26
+ * catch-all can only change the fields its routes name, and a diff on any
27
+ * other field is reported as un-appliable instead of being sent somewhere
28
+ * that will strip it and answer 200.
29
+ */
30
+ readonly fields?: ReadonlyArray<string>;
31
+ }
32
+ export interface ResourceDef {
33
+ /** The key under `resources:` and the word in every plan line. */
34
+ readonly type: string;
35
+ /** Singular, for a sentence. */
36
+ readonly label: string;
37
+ /** The field holding the natural key, in the document and on the live row. */
38
+ readonly naturalKey: string;
39
+ readonly keySource: 'authored' | 'server';
40
+ /** True when the database enforces the key. False when only this file does. */
41
+ readonly keyEnforced: boolean;
42
+ /** The server's own normalisation, applied to both sides before matching. */
43
+ readonly normalizeKey?: (key: string) => string;
44
+ readonly list: {
45
+ readonly path: string;
46
+ readonly paged: boolean;
47
+ };
48
+ /** The field on a live row that identifies it in a write path. */
49
+ readonly idField: string;
50
+ readonly create: {
51
+ readonly path: string;
52
+ /** POST is an upsert keyed on the body's `id`, not a plain create. */
53
+ readonly upsert?: boolean;
54
+ } | null;
55
+ /** Empty means there is no way to change one in place. */
56
+ readonly update: ReadonlyArray<UpdateRoute>;
57
+ /** Said when a changed field matches no update route. */
58
+ readonly updateBlocked?: string;
59
+ /**
60
+ * The write REPLACES the resource: a field the document omits reverts to
61
+ * its default. `plan` says so on every change to one of these.
62
+ */
63
+ readonly fullReplace?: boolean;
64
+ /** `null` when `--prune` cannot delete one; `removeBlocked` says why. */
65
+ readonly remove: {
66
+ readonly path: string;
67
+ } | null;
68
+ readonly removeBlocked?: string;
69
+ /**
70
+ * Fields that are never readable back, so a diff must not claim they
71
+ * changed. Half the answer; the other half is the `__redacted__` sentinel,
72
+ * detected at runtime in `diff.ts`.
73
+ */
74
+ readonly opaque: ReadonlyArray<string>;
75
+ /**
76
+ * Fields a CREATE answers with exactly once. A floor, not a fence:
77
+ * `apply.ts` also sweeps every response for anything that looks like a
78
+ * credential, so a field added next month is surfaced rather than dropped.
79
+ */
80
+ readonly secrets: ReadonlyArray<string>;
81
+ /**
82
+ * Returned on a read and accepted by NO write path. A document declaring
83
+ * one is refused at parse time: zod would strip it and answer 200, and the
84
+ * author would never find out.
85
+ */
86
+ readonly unwritable: ReadonlyArray<string>;
87
+ /** The live row, reshaped into the vocabulary the document writes. */
88
+ readonly project?: (row: Record<string, unknown>) => Record<string, unknown>;
89
+ /** Document-only keys naming another resource by ITS natural key. */
90
+ readonly references?: Readonly<Record<string, ResourceRef>>;
91
+ }
92
+ /** The server's spelling of a key, for matching. */
93
+ export declare function normalizedKey(def: ResourceDef, key: string): string;
94
+ /** The live row in the vocabulary the document writes. */
95
+ export declare function projected(def: ResourceDef, row: Record<string, unknown>): Record<string, unknown>;
96
+ /**
97
+ * Which update route owns a field, or `undefined` if none does.
98
+ *
99
+ * A route with no `fields` is the catch-all and is tried last, so a field a
100
+ * specific route claims (`webhookUrl` → `/v1/bots/{id}/external`) is never
101
+ * swept into the general PATCH.
102
+ */
103
+ export declare function routeForField(def: ResourceDef, field: string): UpdateRoute | undefined;
104
+ /**
105
+ * Anything that reads like a credential, by NAME.
106
+ *
107
+ * `secrets` on each definition is a hand-written list, and a hand-written
108
+ * list goes stale — the failure CLAUDE.md names twice, where a gate's green
109
+ * line counts what it managed to parse rather than what exists. So `apply`
110
+ * also runs every write response through this, and a field called
111
+ * `webhookToken` that nobody told this file about is still surfaced to the
112
+ * operator instead of being dropped on the floor. False positives are cheap:
113
+ * the cost is one extra line of output.
114
+ */
115
+ export declare function looksLikeCredential(field: string): boolean;
116
+ //# sourceMappingURL=resource-def.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-def.d.ts","sourceRoot":"","sources":["../../src/wac/resource-def.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The SHAPE of a resource definition, and the helpers that read one.
3
+ *
4
+ * Split out of `resources.ts` only because the two together ran past the
5
+ * 650-line ceiling (`scripts/check-file-size.mjs`). The division is the
6
+ * useful one anyway: this file is the vocabulary, `resources.ts` is the nine
7
+ * answers written in it, and reading one does not require reading the other.
8
+ * `resources.ts` re-exports everything here, so every importer sees one
9
+ * module.
10
+ */
11
+ /** The server's spelling of a key, for matching. */
12
+ export function normalizedKey(def, key) {
13
+ return def.normalizeKey === undefined ? key : def.normalizeKey(key);
14
+ }
15
+ /** The live row in the vocabulary the document writes. */
16
+ export function projected(def, row) {
17
+ return def.project === undefined ? row : def.project(row);
18
+ }
19
+ /**
20
+ * Which update route owns a field, or `undefined` if none does.
21
+ *
22
+ * A route with no `fields` is the catch-all and is tried last, so a field a
23
+ * specific route claims (`webhookUrl` → `/v1/bots/{id}/external`) is never
24
+ * swept into the general PATCH.
25
+ */
26
+ export function routeForField(def, field) {
27
+ return (def.update.find((r) => r.fields?.includes(field) === true) ??
28
+ def.update.find((r) => r.fields === undefined));
29
+ }
30
+ /**
31
+ * Anything that reads like a credential, by NAME.
32
+ *
33
+ * `secrets` on each definition is a hand-written list, and a hand-written
34
+ * list goes stale — the failure CLAUDE.md names twice, where a gate's green
35
+ * line counts what it managed to parse rather than what exists. So `apply`
36
+ * also runs every write response through this, and a field called
37
+ * `webhookToken` that nobody told this file about is still surfaced to the
38
+ * operator instead of being dropped on the floor. False positives are cheap:
39
+ * the cost is one extra line of output.
40
+ */
41
+ export function looksLikeCredential(field) {
42
+ return /secret|token|password|credential|privatekey|apikey/i.test(field);
43
+ }
44
+ //# sourceMappingURL=resource-def.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-def.js","sourceRoot":"","sources":["../../src/wac/resource-def.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkFH,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,GAAgB,EAAE,GAAW;IACzD,OAAO,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;AACtE,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,SAAS,CAAC,GAAgB,EAAE,GAA4B;IACtE,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,GAAgB,EAAE,KAAa;IAC3D,OAAO,CACL,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;QAC1D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAC/C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,qDAAqD,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * The resource table `chatly plan` and `chatly apply` work from.
3
+ *
4
+ * ═══ WHY A TABLE ══════════════════════════════════════════════════════════
5
+ *
6
+ * The ask behind this feature, in the integrator's words: "we wrote ~600
7
+ * lines to idempotently upsert tools, bots, routes, cards and channel
8
+ * config." Those 600 lines are nine copies of the same four steps — list,
9
+ * index by some stable name, compare, write — differing only in a route and a
10
+ * field name. This file is those differences and nothing else; `diff.ts` is
11
+ * the four steps, once, and `apply.ts` is the writes.
12
+ *
13
+ * ═══ IDENTITY: THE DECISION EVERYTHING ELSE RESTS ON ═════════════════════
14
+ *
15
+ * A declarative file cannot refer to resources by the server's uuid. On a
16
+ * fresh checkout against a fresh workspace there are no uuids, and a file
17
+ * carrying them is a file you cannot apply to a second environment — which
18
+ * is the whole reason this exists ("teams that run more than one
19
+ * environment"). So every resource needs a NATURAL KEY: a field the author
20
+ * writes, that comes back on the list read, and that identifies one row.
21
+ *
22
+ * Read off the handlers and the migrations, the nine are:
23
+ *
24
+ * brands slug UNIQUE (workspace_id, slug)
25
+ * bots slug UNIQUE (workspace_id, slug) WHERE NOT NULL
26
+ * macros shortCode UNIQUE (workspace_id, lower(short_code))
27
+ * emailTemplates kind UNIQUE (workspace_id, kind) — and a path param
28
+ * customerCards key UNIQUE (workspace_id, key)
29
+ * botRoutes name NO CONSTRAINT
30
+ * triggers name NO CONSTRAINT
31
+ * webhooks url NO CONSTRAINT
32
+ * docsSources label NO CONSTRAINT
33
+ * channels publicId UNIQUE — but minted by the server
34
+ *
35
+ * The first five are real: the database refuses a second row, so matching on
36
+ * the key cannot pick the wrong one. The next four are a CONVENTION the
37
+ * document enforces rather than a constraint the server does — so `diff.ts`
38
+ * refuses to touch a key that appears twice on either side instead of
39
+ * guessing, which is the only honest thing to do with a key that can repeat.
40
+ *
41
+ * ── The resource with no natural key at all ──────────────────────────────
42
+ *
43
+ * CHANNELS. `publicId` is required on create but it is a RESERVATION, not a
44
+ * name you choose freely: `channels_public_id_unique` is global rather than
45
+ * workspace-scoped, so another tenant may already hold `web`. `type` and
46
+ * `publicId` are both immutable afterwards, `config` reads back with
47
+ * `__redacted__` in every credential key, and the create response carries
48
+ * per-provider webhook registration results that a diff cannot model. A
49
+ * `channels:` section is therefore REFUSED by `document.ts`, by name, with
50
+ * the reason — not half-supported. `chatly channels create|update` is the
51
+ * way, and that is what the refusal says.
52
+ *
53
+ * ═══ VERIFIED AGAINST HANDLERS, NOT AGAINST NAMES ════════════════════════
54
+ *
55
+ * Every path, key, `paged` flag, update field-set and projection below was
56
+ * read off `apps/api/src/modules/**` and `packages/db/migrations/**`.
57
+ * CLAUDE.md records why that is not optional: the SDK manifest's first
58
+ * version listed two routes that do not exist and had its `paged` flag wrong
59
+ * in BOTH directions, and a fake transport that answers every route with a
60
+ * page envelope agrees with whatever the manifest says. Seven of the nine
61
+ * list routes here are `@PaginationExempt` bare arrays; treating one as a
62
+ * page is the bug two SDKs shipped.
63
+ *
64
+ * Six things in this table exist only because a handler said so, and each
65
+ * would be a live bug if guessed:
66
+ *
67
+ * 1. `macros.shortCode` does not round-trip. `normaliseShortCode` strips a
68
+ * leading `/` and lowercases, so a document saying `/Refund` would never
69
+ * match the stored `refund`, would create on every run, and would then
70
+ * collide with the unique index forever. Hence `normalizeKey`.
71
+ * 2. `emailTemplates` keeps the customised copy under `override`, not at the
72
+ * top level, and the list always returns all five kinds with
73
+ * `override: null` for the ones on the built-in. A naive diff reads
74
+ * `row.subject` — which does not exist — and reports a change every run.
75
+ * Hence `project`.
76
+ * 3. A bot's external config comes back nested under `externalAgent` and is
77
+ * written to a DIFFERENT route (`PATCH /v1/bots/{id}/external`). Hence
78
+ * `project` plus a second update route.
79
+ * 4. `webhooks` has NO general PATCH. Only the pinned schema version can
80
+ * change; `url`, `eventTypes` and `description` are frozen at create.
81
+ * 5. `triggers` POST is a FULL REPLACE keyed on the body's `id`, and an
82
+ * unknown id is a 404 rather than a create. So the id must come from the
83
+ * live row, and an omitted field is RESET to its default — hence
84
+ * `fullReplace`, which makes `plan` say so out loud.
85
+ * 6. `docsSources` PATCH accepts six fields. `provider`, `spaceId`,
86
+ * `organizationId`, `includePaths` and `excludePaths` are create-only, so
87
+ * a change to one of them is reported as un-appliable rather than sent to
88
+ * a route that will strip it and answer 200.
89
+ */
90
+ export type { Action, ResourceDef, ResourceRef, UpdateRoute } from './resource-def.js';
91
+ export { looksLikeCredential, normalizedKey, projected, routeForField } from './resource-def.js';
92
+ import type { ResourceDef } from './resource-def.js';
93
+ /**
94
+ * Dependency order, and it is the array order.
95
+ *
96
+ * `botRoutes` after `bots` because a route names a bot, and in a single
97
+ * `apply` that bot may not exist until this run creates it. Nothing else
98
+ * references anything else, so the rest is ordered by how likely somebody is
99
+ * to be looking for it.
100
+ */
101
+ export declare const RESOURCES: ReadonlyArray<ResourceDef>;
102
+ export declare const RESOURCE_TYPES: ReadonlyArray<string>;
103
+ export declare function resourceDef(type: string): ResourceDef | undefined;
104
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/wac/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAEH,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEjG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAWrD;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,WAAW,CAibhD,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAAa,CAAC,MAAM,CAAgC,CAAC;AAIlF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEjE"}