@instruments/materialgraph 0.28.0 → 0.30.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.
Files changed (80) hide show
  1. package/brands-client.d.ts +1 -1
  2. package/by-colour-types.d.ts +4 -4
  3. package/catalogue-client.d.ts +3 -3
  4. package/catalogue-client.js +3 -3
  5. package/catalogue-types.d.ts +1 -1
  6. package/client-vocabulary.d.ts +3 -5
  7. package/client-vocabulary.js +9 -6
  8. package/client.d.ts +73 -174
  9. package/client.js +85 -211
  10. package/colour-region-types.d.ts +18 -0
  11. package/colours-client.d.ts +10 -83
  12. package/colours-client.js +1 -85
  13. package/find-variants-by-colour-types.d.ts +197 -0
  14. package/find-variants-by-vector-types.d.ts +67 -0
  15. package/find-variants-by-vector-types.js +5 -0
  16. package/get-variant-types.d.ts +50 -0
  17. package/get-variant-types.js +10 -0
  18. package/identifiers.d.ts +1 -1
  19. package/identifiers.js +1 -1
  20. package/image-reference-types.d.ts +18 -0
  21. package/index.d.ts +8 -7
  22. package/index.js +1 -1
  23. package/list-enum-values-types.d.ts +58 -0
  24. package/list-variants-types.d.ts +6 -2
  25. package/material-asset-types.d.ts +4 -3
  26. package/material-types.d.ts +11 -150
  27. package/operations.d.ts +1 -1
  28. package/operations.js +26 -88
  29. package/package.json +1 -1
  30. package/palette-search-types.d.ts +3 -49
  31. package/resolve-colour-types.d.ts +5 -5
  32. package/resolve-variant-types.d.ts +75 -0
  33. package/{identify-types.js → resolve-variant-types.js} +2 -2
  34. package/response-schemas.generated.d.ts +1 -1
  35. package/response-schemas.generated.js +45 -84
  36. package/run-types.d.ts +23 -216
  37. package/runs.d.ts +10 -58
  38. package/runs.js +19 -106
  39. package/schema-resource-types.d.ts +2 -139
  40. package/schema-resource-types.js +0 -8
  41. package/search-card-types.d.ts +1 -1
  42. package/search-index-plan-types.d.ts +4 -1
  43. package/search-request-types.d.ts +10 -2
  44. package/spec-types.d.ts +4 -4
  45. package/spec-types.js +2 -2
  46. package/sse.d.ts +2 -2
  47. package/sse.js +2 -2
  48. package/types.d.ts +4 -38
  49. package/webmcp.generated.js +3 -3
  50. package/workflows-client.d.ts +54 -0
  51. package/workflows-client.js +41 -0
  52. package/client-source-material-comments.d.ts +0 -51
  53. package/client-source-material-comments.js +0 -6
  54. package/codes-client.d.ts +0 -118
  55. package/codes-client.js +0 -99
  56. package/codes-types.d.ts +0 -325
  57. package/colour-aggregate-types.d.ts +0 -163
  58. package/coordinate-client.d.ts +0 -23
  59. package/coordinate-client.js +0 -13
  60. package/demand-batch-types.d.ts +0 -118
  61. package/demand-types.d.ts +0 -154
  62. package/identify-types.d.ts +0 -82
  63. package/material-composition-client.d.ts +0 -21
  64. package/material-composition-client.js +0 -13
  65. package/material-composition-types.d.ts +0 -403
  66. package/material-composition-types.js +0 -1
  67. package/source-material-validation.d.ts +0 -7
  68. package/source-material-validation.js +0 -214
  69. package/spec-facet-types.d.ts +0 -48
  70. package/spec-facet-types.js +0 -7
  71. package/substitute-types.d.ts +0 -102
  72. package/substitute-types.js +0 -1
  73. package/suggest-types.d.ts +0 -67
  74. package/suggest-types.js +0 -28
  75. package/taxonomy-coverage-types.d.ts +0 -34
  76. package/taxonomy-coverage-types.js +0 -1
  77. /package/{codes-types.js → colour-region-types.js} +0 -0
  78. /package/{colour-aggregate-types.js → find-variants-by-colour-types.js} +0 -0
  79. /package/{demand-batch-types.js → image-reference-types.js} +0 -0
  80. /package/{demand-types.js → list-enum-values-types.js} +0 -0
package/client.js CHANGED
@@ -3,20 +3,17 @@ import { listBrands } from "./brands-client.js";
3
3
  import { CatalogueClient } from "./catalogue-client.js";
4
4
  import { CategoriesClient } from "./categories-client.js";
5
5
  import { DEFAULT_BASE_URL } from "./client-config.js";
6
- import { createSourceMaterialCommentsNamespace } from "./client-source-material-comments.js";
7
- import { listAttributes as listAttributesCall, resolveColour as resolveColourCall, suggest as suggestCall, suggestColour as suggestColourCall, } from "./client-vocabulary.js";
8
- import { CodesClient } from "./codes-client.js";
6
+ import { listAttributes as listAttributesCall, listEnumValues as listEnumValuesCall, resolveColour as resolveColourCall, } from "./client-vocabulary.js";
9
7
  import { ColoursClient } from "./colours-client.js";
10
- import { coordinate as coordinateCall } from "./coordinate-client.js";
11
8
  import { MaterialGraphError } from "./errors.js";
12
9
  import { HttpTransport } from "./http-transport.js";
13
10
  import { matchStream as matchStreamCall } from "./match-stream.js";
14
- import { compositionReport as compositionReportCall } from "./material-composition-client.js";
15
11
  import { listVariantAssets as listVariantAssetsCall, materialBlocks as materialBlocksCall, materialDocuments as materialDocumentsCall, } from "./material-resources-client.js";
16
12
  // Aliased to `operation` — the local name every call site here already used.
17
13
  import { resolveOperation as operation } from "./operations.js";
18
- import { createRun, drainRunEvents, getRun, getSourceMaterialBatch as getSourceMaterialBatchCall, streamRunEvents, uploadSourceMaterial, } from "./runs.js";
14
+ import { uploadSourceMaterial } from "./runs.js";
19
15
  import { SCHEMA_RESOURCE_OPERATION_IDS } from "./schema-resource-types.js";
16
+ import { WorkflowsClient } from "./workflows-client.js";
20
17
  // Re-exported whole so `index.ts` and every existing importer keep their path.
21
18
  export * from "./client-config.js";
22
19
  /**
@@ -28,16 +25,15 @@ export * from "./client-config.js";
28
25
  * | The question | The lens |
29
26
  * | --- | --- |
30
27
  * | "find me something like this / matching these words" | {@link MaterialGraphClient.search} |
28
+ * | "tell me about THIS variant" | {@link MaterialGraphClient.getVariant} |
31
29
  * | "what's IN this photo?" | {@link MaterialGraphClient.match} / {@link MaterialGraphClient.matchStream} |
32
30
  * | "which materials, from anyone, meet this hard spec?" | {@link MaterialGraphClient.listVariants} |
33
- * | "what's near this colour / this palette / this gradient?" | {@link MaterialGraphClient.byColour}, `colours.palette()`, `colours.gradient()` |
34
- * | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.listVariants} (same product, other colourways), {@link MaterialGraphClient.coordinate} |
31
+ * | "what's near this colour / this palette?" | {@link MaterialGraphClient.byColour}, `colours.palette()` |
32
+ * | "what goes with THIS material?" | {@link MaterialGraphClient.pairings}, {@link MaterialGraphClient.listVariants} (same product, other colourways) |
35
33
  * | "what does the manufacturer say is AVAILABLE on this?" | {@link MaterialGraphClient.associatedFinishes}, {@link MaterialGraphClient.associatedProducts} |
36
- * | "how much is this specified?" | {@link MaterialGraphClient.demand}, {@link MaterialGraphClient.demandBatch} |
37
- * | "is this allowed in an A-2 corridor?" | `codes.*` |
38
- * | "what can I even ask for?" | {@link MaterialGraphClient.specificationFacets}, `categories.list()` |
34
+ * | "what can I even ask for?" | {@link MaterialGraphClient.listVariantFacets}, `categories.list()` |
39
35
  * | "what is this word called here?" | {@link MaterialGraphClient.listAttributes} |
40
- * | "do the whole job for me, async" | {@link MaterialGraphClient.createRun} + `mg:compose` |
36
+ * | "do the whole job for me, async" | `workflows.start()` + `mg:compose` |
41
37
  *
42
38
  * Three structural rules that fall out of that table:
43
39
  * - **Colour is a separate lens.** {@link MaterialGraphClient.search} matches
@@ -71,35 +67,25 @@ export class MaterialGraphClient extends CatalogueClient {
71
67
  */
72
68
  categories;
73
69
  /**
74
- * THE QUESTION: "is this material allowed HERE?" — a jurisdiction + occupancy +
75
- * finish location, not a property of the material alone. `codes.context()`
76
- * discovers the vocabulary, `codes.requirements()` states a space,
77
- * `codes.advisory()` badges candidates.
78
- *
79
- * Instead use {@link MaterialGraphClient.listVariants} to find materials by a
80
- * classification you already know you need; codes is what tells you WHICH one
81
- * the space demands. ADVISORY ONLY: model code as adopted, never a
82
- * determination for an authority having jurisdiction.
83
- */
84
- codes;
85
- /**
86
- * THE QUESTION: "what colours exist here?" (`profiles`, `stats`) and "what
87
- * matches this whole SET of colours?" (`palette`, `gradient`).
70
+ * THE QUESTION: "what colours exist here?" and "what matches this whole SET
71
+ * of colours?" (`palette`).
88
72
  *
89
73
  * Instead use {@link MaterialGraphClient.byColour} for ONE colour and
90
74
  * {@link MaterialGraphClient.listVariants} to page through a colour's family.
91
75
  * Colour is usually derived from product imagery, which is visual likeness,
92
- * not a colour specification. A maker's declared colour, such as a paint's
76
+ * not a colour specification. A brand's declared colour, such as a paint's
93
77
  * published hex, wins where MaterialGraph holds one, and each value's
94
78
  * source basis says which.
95
79
  */
96
80
  colours;
97
81
  /**
98
- * THE QUESTION: "what did people say about this uploaded file, and record what
99
- * they said." Names on these remarks are REPORTED, never verified — see
100
- * {@link SourceMaterialCommentsNamespace}.
82
+ * THE QUESTION: "do a whole job for me" — the ASYNC lens. `start()` begins a
83
+ * Run of one registered workflow, `run()` polls it, `streamRunEvents()` and
84
+ * `drainRunEvents()` follow it live. Everything else on this client answers
85
+ * in one round trip; a Run may suspend for human input and finish minutes
86
+ * later. Replaces the flat `createRun()` / `run()` family, with no shim.
101
87
  */
102
- sourceMaterialComments;
88
+ workflows;
103
89
  #transport;
104
90
  constructor(options) {
105
91
  if (!options.apiKey) {
@@ -116,15 +102,11 @@ export class MaterialGraphClient extends CatalogueClient {
116
102
  this.categories = new CategoriesClient({
117
103
  get: async (operationId, query, signal, pathParams) => await this.#get(operationId, query, signal, pathParams),
118
104
  });
119
- this.codes = new CodesClient({
120
- get: async (operationId, query, signal) => await this.#get(operationId, query, signal),
121
- post: async (operationId, body, signal) => await this.#post(operationId, body, signal),
122
- });
123
105
  this.colours = new ColoursClient({
124
106
  get: async (operationId, query, signal) => await this.#get(operationId, query, signal),
125
107
  post: async (operationId, body, signal) => await this.#post(operationId, body, signal),
126
108
  });
127
- this.sourceMaterialComments = createSourceMaterialCommentsNamespace(this.#transport);
109
+ this.workflows = new WorkflowsClient({ transport });
128
110
  }
129
111
  get lastRateLimit() {
130
112
  return this.#transport.lastRateLimit;
@@ -200,7 +182,7 @@ export class MaterialGraphClient extends CatalogueClient {
200
182
  * palette), ranked by OKLab closeness to colour extracted from product imagery.
201
183
  *
202
184
  * Instead use `colours.palette()` for N colours at stated proportions (a
203
- * different ranking, not a wrapper), `colours.gradient()` for a transition,
185
+ * different ranking, not a wrapper),
204
186
  * {@link MaterialGraphClient.resolveColour} first if you hold a NAME,
205
187
  * {@link MaterialGraphClient.listVariants} to page through a category or a brand, and
206
188
  * {@link MaterialGraphClient.search} when colour is one signal among many.
@@ -227,36 +209,33 @@ export class MaterialGraphClient extends CatalogueClient {
227
209
  }
228
210
  // ── Materials ──────────────────────────────────────────────────────────────
229
211
  /**
230
- * THE QUESTION: "tell me everything about THIS material" (GET
231
- * `/api/v1/materials/{variantId}`).
212
+ * THE QUESTION: "tell me about THIS variant" (GET
213
+ * `/api/v1/variants/{variantId}`, `get_variant`).
232
214
  *
233
215
  * The hinge of the API: every relationship lens
234
- * ({@link MaterialGraphClient.listVariants}, {@link MaterialGraphClient.pairings},
235
- * {@link MaterialGraphClient.demand}, `codes.advisory()`) hangs off a
236
- * `variantId`. Instead use {@link MaterialGraphClient.listVariants} for many
237
- * ids, or for the other colourways of the same product (MG-2222 retired
238
- * this route's `?include=siblings`; ask `listVariants` with a `product in`
239
- * filter instead), {@link MaterialGraphClient.materialBlocks} to tell "no
240
- * value" from "not checked", {@link MaterialGraphClient.search} when you
241
- * have no id yet.
216
+ * ({@link MaterialGraphClient.listVariants}, {@link MaterialGraphClient.pairings})
217
+ * hangs off a `variantId`. The answer is compact on purpose: name, code,
218
+ * product, brand, category, image and page. Name `attributes` and `blocks`
219
+ * to add their values under `specifications`; a null value is unknown, never
220
+ * false or zero. Instead use {@link MaterialGraphClient.listVariants} for many
221
+ * ids, or for the other colourways of the same product (ask `listVariants`
222
+ * with a `product in` filter),
223
+ * {@link MaterialGraphClient.materialBlocks} to tell "no value" from "not
224
+ * checked", {@link MaterialGraphClient.search} when you have no id yet.
242
225
  *
243
226
  * @param variantId Opaque — pass back exactly what you were given.
244
227
  */
245
- async material(variantId, signal) {
246
- return await this.#get("get_material", undefined, signal, { variantId });
228
+ async getVariant(variantId, options, signal) {
229
+ return await this.#get("get_variant", {
230
+ attributes: options?.attributes?.length
231
+ ? options.attributes.join(",")
232
+ : undefined,
233
+ blocks: options?.blocks?.length ? options.blocks.join(",") : undefined,
234
+ }, signal, { variantId });
247
235
  }
248
236
  async materialBlocks(variantId, signal) {
249
237
  return await materialBlocksCall(this.#transport, variantId, signal);
250
238
  }
251
- /**
252
- * THE QUESTION: "which product types can composition be answered for yet?"
253
- *
254
- * Coverage per product type, in Live SKUs as well as tuples. Read the SKU
255
- * figure: one tuple can carry a hundred thousand SKUs or one.
256
- */
257
- async compositionReport(signal) {
258
- return await compositionReportCall(this.#transport, signal);
259
- }
260
239
  async listVariantAssets(variantId, options, signal) {
261
240
  return await listVariantAssetsCall(this.#transport, variantId, options, signal);
262
241
  }
@@ -266,7 +245,7 @@ export class MaterialGraphClient extends CatalogueClient {
266
245
  /**
267
246
  * THE QUESTION: "what does the schema DEFINE?" — the governed static registry.
268
247
  *
269
- * Definitions, not data: use {@link MaterialGraphClient.specificationFacets} for
248
+ * Definitions, not data: use {@link MaterialGraphClient.listVariantFacets} for
270
249
  * what the corpus actually holds.
271
250
  */
272
251
  async schema(resource, signal) {
@@ -276,8 +255,7 @@ export class MaterialGraphClient extends CatalogueClient {
276
255
  * THE QUESTION: "what goes WITH this material?" — the companionship rail, hung
277
256
  * off one resolved material (GET `/api/v1/materials/{variantId}/pairings`).
278
257
  *
279
- * Instead use {@link MaterialGraphClient.coordinate} for a whole palette of
280
- * picks, {@link MaterialGraphClient.listVariants} for the same product in
258
+ * Instead use {@link MaterialGraphClient.listVariants} for the same product in
281
259
  * another colour, and `search({ similarTo })` for a LIKE-THIS substitute —
282
260
  * similar and complementary are opposite questions.
283
261
  *
@@ -310,10 +288,11 @@ export class MaterialGraphClient extends CatalogueClient {
310
288
  * of a product (`{terms: [{field: "product", op: "in", values:
311
289
  * [productId]}, {field: "variant", op: "not_in", values: [variantId]}]}` —
312
290
  * `filters` is kind-grouped, `{terms, numbers, presence}`, every condition
313
- * across all three ANDed), or a category/maker listing. An explicit
291
+ * across all three ANDed), or a category/brand listing. An explicit
314
292
  * `variant in` id that does not resolve to an eligible variant is
315
- * reported in `unresolvedVariantIds`, never silently dropped from the
316
- * page.
293
+ * reported in `unresolvedVariantIds`, or in `excludedByCorpusScope` when
294
+ * it names a real variant outside the materials catalogue, never silently
295
+ * dropped from the page.
317
296
  *
318
297
  * `filters`/`optionalFilters`/`orderBy` are sent as URL-encoded JSON;
319
298
  * `attributes`/`include` as comma-separated lists — the server validates,
@@ -349,18 +328,35 @@ export class MaterialGraphClient extends CatalogueClient {
349
328
  async listVariantFacets(input, signal) {
350
329
  return await this.#post("list_variant_facets", input, signal);
351
330
  }
331
+ /**
332
+ * THE QUESTION: "which variants hold, resemble, or can be ranked by, this
333
+ * colour?" (POST `/api/v1/variants/by-colour`, MG-2217/MG-2230/MG-2247).
334
+ * One required `mode`: `contains` (a swatch for every required `colours[]`
335
+ * condition), `palette` (whole-palette closeness to `colours[]`, an
336
+ * image's extracted palette, or `targetVariantId`'s own palette) or
337
+ * `rerank` (reorder a held `variantIds` set by colour closeness). Use
338
+ * {@link MaterialGraphClient.byColour} instead for ONE colour against the
339
+ * search index rather than the canonical variant graph.
340
+ */
341
+ async findVariantsByColour(input, signal) {
342
+ return await this.#post("find_variants_by_colour", input, signal);
343
+ }
344
+ /**
345
+ * THE QUESTION: "what looks or feels like this?" (POST
346
+ * `/api/v1/variants/by-vector`, MG-2231). Eligible variants ranked by
347
+ * semantic or visual similarity to a text description (mood words count),
348
+ * an image reference, or both fused into one embedding. `filters` is the
349
+ * same tree {@link MaterialGraphClient.listVariants} takes. `query.mode`
350
+ * values other than `"text"` embed through Voyage and spend the shared
351
+ * per-key `embed` rate budget.
352
+ */
353
+ async findVariantsByVector(input, signal) {
354
+ return await this.#post("find_variants_by_vector", input, signal);
355
+ }
352
356
  /** Resolve a colour name to its hex, and the named paint's catalogue variant. */
353
357
  async resolveColour(name, signal, options) {
354
358
  return await resolveColourCall(this.#transport, name, signal, options);
355
359
  }
356
- /** Ranked colour-name typeahead (GET `/api/v1/search/colours/suggest`). */
357
- async suggestColour(q, options, signal) {
358
- return await suggestColourCall(this.#transport, q, options, signal);
359
- }
360
- /** Entity typeahead. Navigate using the selected `refId`; use `search()` for materials. */
361
- async suggest(q, options, signal) {
362
- return await suggestCall(this.#transport, q, options, signal);
363
- }
364
360
  // ── Catalog ──────────────────────────────────────────────────────────────────
365
361
  /**
366
362
  * THE QUESTION: "which attribute means this phrase?" or "what could a
@@ -374,24 +370,28 @@ export class MaterialGraphClient extends CatalogueClient {
374
370
  async listAttributes(options, signal) {
375
371
  return await listAttributesCall(this.#transport, options, signal);
376
372
  }
373
+ /**
374
+ * THE QUESTION: "which value means this phrase?" or "what may this attribute
375
+ * be set to?" (GET `/api/v1/catalog/enum-values`). One page of a value
376
+ * dictionary's allowed values, browsed by `dictionaryId` (from a
377
+ * {@link MaterialGraphClient.listAttributes} item) or found by name, alias,
378
+ * misspelling or meaning across every dictionary. Pass an item's `valueId`
379
+ * with one of its `attributeIds` to {@link MaterialGraphClient.listVariants};
380
+ * an item with no `attributeIds` cannot be filtered on. Follow
381
+ * `pageInfo.endCursor` for the next page.
382
+ */
383
+ async listEnumValues(options, signal) {
384
+ return await listEnumValuesCall(this.#transport, options, signal);
385
+ }
377
386
  // Hard-specification search (formerly `bySpec()`, GET
378
387
  // `/api/v1/search/specifications`) moved to {@link MaterialGraphClient.listVariants}
379
388
  // (MG-2222): each `attribute:value` criterion is one `filters` condition —
380
389
  // `{field: attributeId, op: "in", values: [...]}` in `filters.terms` for a
381
390
  // term, `{field: attributeId, op: "gte"/"lte"/etc, value}` in
382
391
  // `filters.numbers` for a number — and every condition across the
383
- // kind-grouped `filters` object must match. Use `codes.advisory()` when
384
- // the real question is what a space demands, and {@link specificationFacets}
392
+ // kind-grouped `filters` object must match. See {@link listVariantFacets}
385
393
  // first if you do not know the canonical tokens.
386
394
  // ── Coverage + meta ──────────────────────────────────────────────────────────
387
- /**
388
- * Exact current primary-type status counts by retained source category path.
389
- * `comparisonThreshold` is a reporting cutoff only: it never promotes a
390
- * proposal into an accepted category.
391
- */
392
- async productTypeCoverage(options, signal) {
393
- return await this.#get("get_product_type_status_coverage", options, signal);
394
- }
395
395
  /**
396
396
  * THE QUESTION: "what is this key allowed to do?" Check `scopes` before
397
397
  * offering a capability: the write surfaces need `mg:ingest` / `mg:compose`,
@@ -400,66 +400,6 @@ export class MaterialGraphClient extends CatalogueClient {
400
400
  async whoami(signal) {
401
401
  return await this.#get("whoami", undefined, signal);
402
402
  }
403
- // ── Pairings / demand ────────────────────────────────────────────────────────
404
- /** What completes a scheme already part-chosen. See {@link coordinateCall}. */
405
- async coordinate(input, signal) {
406
- return await coordinateCall(this.#transport, input, signal);
407
- }
408
- /**
409
- * THE QUESTION: "how much is this material actually specified, and what gets
410
- * specified alongside it?" — real order behaviour, trailing 36 months.
411
- *
412
- * Instead use {@link MaterialGraphClient.demandBatch} for many ids (this one
413
- * also resolves free text, which the batch deliberately does not) and
414
- * {@link MaterialGraphClient.pairings} for a presentable rail. A null `demand`
415
- * means UNREAD, never zero. Provide EXACTLY ONE of `variantId` or `material`.
416
- */
417
- async demand(params, signal) {
418
- return await this.#get("get_material_demand", { material: params.material, variantId: params.variantId }, signal);
419
- }
420
- /**
421
- * THE QUESTION: "for each of these picks I already hold, how much momentum does
422
- * it have?" Enrichment, never discovery or ranking. Instead use
423
- * {@link MaterialGraphClient.demand} for one material named in free text and
424
- * {@link MaterialGraphClient.search} to find candidates at all. Only
425
- * `state: "available"` carries numbers; the other three mean "no number", never
426
- * "no demand".
427
- *
428
- * Enrich a bounded set of ALREADY-CHOSEN candidate ids with their real
429
- * specification demand (POST `/api/v1/demand/batch`, 1–48 ids). The bounded
430
- * batch complement to {@link demand}: no fuzzy resolution, ranking, or
431
- * reordering — each id yields one discriminated `state` (`available` /
432
- * `insufficient_data` / `no_signal` / `unknown_variant`) in input order and
433
- * cardinality. This is the Desk's "show demand momentum for every pick in this
434
- * scheme" read; an `mg:admin` key additionally sees per-result `evidence`.
435
- *
436
- * @example
437
- * // Rank a shortlist by real specification momentum before locking the scheme.
438
- * const { results } = await mg.demandBatch({
439
- * variantIds: scheme.picks.map((p) => p.variantId),
440
- * signal: "sampled",
441
- * context: { projectType: "hospitality" },
442
- * });
443
- * const rising = results.filter(
444
- * (r) => r.state === "available" && r.momentum.state === "rising",
445
- * );
446
- */
447
- async demandBatch(input, signal) {
448
- return await this.#post("get_demand_batch", input, signal);
449
- }
450
- // ── Codes (advisory building-code compliance) ────────────────────────────────
451
- /**
452
- * THE QUESTION: "may I put these materials in an A-2 corridor in Texas?"
453
- *
454
- * Badge materials against the advisory building-code registry (POST
455
- * `/api/v1/codes/advisory`). Identical to {@link CodesClient.advisory}, kept
456
- * flat because it shipped before the `codes` namespace existed — prefer
457
- * `mg.codes.advisory()` in new code, and start at `mg.codes.context()` to
458
- * discover every accepted context value. `unverified` is not a pass.
459
- */
460
- async codeAdvisory(input, signal) {
461
- return await this.codes.advisory(input, signal);
462
- }
463
403
  // ── Colour helpers (extract) ─────────────────────────────────────────────────
464
404
  /**
465
405
  * THE QUESTION: "what colours are in this image?" — the palette only, no
@@ -471,89 +411,23 @@ export class MaterialGraphClient extends CatalogueClient {
471
411
  async extractPalette(input, signal) {
472
412
  return await this.#post("extract_image_palette", input, signal);
473
413
  }
474
- // ── Catalog (facets / glossary) ─────────────────────────────────────────────
475
- /**
476
- * THE QUESTION: "what can I even filter on, and what values does the corpus
477
- * actually hold?" The start of every spec-driven workflow. Instead use
478
- * {@link MaterialGraphClient.listAttributes} with `categoryIds` for what a
479
- * product type COULD carry, {@link MaterialGraphClient.schema} for the definition.
480
- *
481
- * Discover the live specification vocabulary (GET
482
- * `/api/v1/catalog/specifications/facets`) — the facetable spec attributes that
483
- * have real product-graph data, each with its top canonical values and the
484
- * variants/brands behind them. This is where a caller LEARNS that the data
485
- * exists: LEED, GREENGUARD, ASTM E84 Class A, FloorScore, EPDs all surface here
486
- * with counts. Includes the virtual `certification_programme` facet (its values
487
- * are programme names). Learn an `attribute:value` here, then reuse it as a
488
- * {@link MaterialGraphClient.listVariants} filter leaf or a {@link search}
489
- * `filters` entry. NOTE: continuous
490
- * numeric specs (e.g. `slip_resistance_dcof`, a 0–1 DCOF range) are NOT
491
- * token-facetable and do not appear here — read those per-attribute.
492
- */
493
- async specificationFacets(signal) {
494
- return await this.#get("list_spec_facets", undefined, signal);
495
- }
496
414
  // ── Brands ───────────────────────────────────────────────────────────────────
497
415
  /** "Whose catalogs are in here, and how big is each?" — {@link listBrands}. */
498
416
  async brands(options = {}, signal) {
499
417
  return await listBrands(this.#transport, options, signal);
500
418
  }
501
- // ── Runs (the hosted agentic surface) ────────────────────────────────────────
419
+ // ── Source material ──────────────────────────────────────────────────────────
502
420
  /**
503
421
  * Upload one atomic source-material batch (POST `/api/v1/source-material`,
504
422
  * multipart). Pass a heterogeneous pile via `{ files }` (any mix of PDF,
505
- * text/markdown, and images — the server classifies each) or the legacy
506
- * `{ brief, references }` shape. Requires the `mg:ingest` scope.
423
+ * text/markdown, and images — the server classifies each). Requires the
424
+ * `mg:source` scope, which a `source`, `ingest` or `admin` key carries.
507
425
  */
508
426
  async uploadSourceMaterial(input, signal) {
509
427
  return await uploadSourceMaterial(this.#transport, input, signal);
510
428
  }
511
- /**
512
- * Read back an uploaded batch (GET `/api/v1/source-material/{batchId}`): every
513
- * file with a fetchable `url` and its analyses inlined. `analyses` is always
514
- * an array — `[]` means nothing has been computed yet. Analyses are shared by
515
- * content hash, so re-uploading an already-analysed image returns its analysis
516
- * immediately. A batch you do not own raises the same 404 as one that does not
517
- * exist. Requires the `mg:compose` scope.
518
- */
519
- async sourceMaterialBatch(batchId, signal) {
520
- return await getSourceMaterialBatchCall(this.#transport, batchId, signal);
521
- }
522
- /**
523
- * THE QUESTION: "do a whole job for me" — the ASYNC lens. Everything else here
524
- * answers in one round trip; a Run may suspend for human input and finish
525
- * minutes later. Follow with {@link MaterialGraphClient.streamRunEvents} (live)
526
- * or {@link MaterialGraphClient.run} (poll). Pass `idempotencyKey` so a retry
527
- * replays rather than duplicates.
528
- */
529
- async createRun(body, signal) {
530
- return await createRun(this.#transport, body, signal);
531
- }
532
- /**
533
- * THE QUESTION: "where has this Run got to?" — the caller-owned, redacted
534
- * ledger record. Polling; prefer {@link MaterialGraphClient.streamRunEvents}
535
- * when a human is waiting.
536
- */
537
- async run(runId, signal) {
538
- return await getRun(this.#transport, runId, signal);
539
- }
540
429
  /** Follow a verdict cell's citation handle to its canonical claim source. */
541
430
  async citation(citationId, signal) {
542
431
  return await this.#get("get_verdict_citation", undefined, signal, { citationId });
543
432
  }
544
- /**
545
- * Stream ordered Run events as an async iterator (GET
546
- * `/api/v1/runs/{runId}/events`, SSE). Iterates until the stream ends or the
547
- * signal aborts; pass `after` to resume from the last observed sequence.
548
- */
549
- streamRunEvents(runId, options) {
550
- return streamRunEvents(this.#transport, runId, options);
551
- }
552
- /**
553
- * Collect Run events until a terminal event, the deadline, or the stream ends
554
- * — the bounded events-log pattern (a single pass over {@link streamRunEvents}).
555
- */
556
- async drainRunEvents(runId, options) {
557
- return await drainRunEvents(this.#transport, runId, options);
558
- }
559
433
  }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A colour word's region in OKLCh: what a name like "sage" covers, rather than
3
+ * one point. A swatch counts inside it, so it is a box (lightness, chroma, hue)
4
+ * and not a distance. Search echoes it on the plan's colour and accepts it back
5
+ * on the request's `colour`, and the colour lookups carry it on each resolved
6
+ * region.
7
+ */
8
+ export type ColourRegionRange = {
9
+ /** Inclusive OKLab lightness bounds, 0 to 1. */
10
+ lightness: [number, number];
11
+ /** Inclusive OKLCh chroma bounds. */
12
+ chroma: [number, number];
13
+ /**
14
+ * The hue arc in degrees, 0 to 360, inclusive. It wraps when its start is past
15
+ * its end (reds). `null` means any hue, which is what a neutral is.
16
+ */
17
+ hue: [number, number] | null;
18
+ };
@@ -1,28 +1,21 @@
1
- import type { CategoryColourProfilesResponse, ColourProfilesParams, ColourStatsParams, ColourStatsResponse } from "./colour-aggregate-types.js";
2
1
  import type { QueryParams } from "./http-transport.js";
3
2
  import type { SdkOperationId } from "./operations.js";
4
3
  /**
5
- * The colour surface, namespaced as `mg.colours` — the aggregate reads
6
- * (`stats`, `profiles`) plus MULTI-COLOUR search (`palette`, `gradient`).
4
+ * The colour surface, namespaced as `mg.colours` — MULTI-COLOUR search
5
+ * (`palette`).
7
6
  *
8
- * One colour family rather than four more flat methods on an already-large
9
- * client facade: "how much colour does the graph hold at all" (`stats`), "what
10
- * colours does this category come in" (`profiles`), "what goes with these
11
- * colours" (`palette`), and "what moves from this colour to that one"
12
- * (`gradient`). `mg.codes` sets the precedent for a namespace when a
13
- * capability is a family rather than a single verb.
7
+ * A namespace rather than a flat method on an already-large client facade:
8
+ * "what goes with these colours" (`palette`). `mg.codes` set the precedent for
9
+ * a namespace when a capability is a family rather than a single verb.
14
10
  *
15
11
  * The namespace holds no transport of its own: the client hands it a bound
16
12
  * `get`/`post`, so auth, rate-limit accounting and error shaping stay in one
17
13
  * place.
18
14
  *
19
- * The two AGGREGATE responses carry a `basis`. Those are reads of a
20
- * materialized view refreshed on a schedule, not on write, so a caller can and
21
- * should check how much of the corpus the answer actually covers before
22
- * treating it as complete. The two SEARCH responses are live and uncached.
15
+ * `palette`'s response is live and uncached.
23
16
  *
24
- * ROUTING OUT OF THIS NAMESPACE. Colour is a lens, and these are not the only
25
- * doors into it:
17
+ * ROUTING OUT OF THIS NAMESPACE. Colour is a lens, and this is not the only
18
+ * door into it:
26
19
  * - ONE colour → `mg.byColour({ hex })`. This namespace's `palette` is for N
27
20
  * colours at stated proportions, which is a different ranking, not a
28
21
  * convenience wrapper.
@@ -35,7 +28,7 @@ import type { SdkOperationId } from "./operations.js";
35
28
  * FROM PRODUCT IMAGERY, not declared by the manufacturer. Report visual
36
29
  * likeness, never a stated colour specification.
37
30
  */
38
- import type { SearchGradientInput, SearchGradientResponse, SearchPaletteInput, SearchPaletteResponse } from "./palette-search-types.js";
31
+ import type { SearchPaletteInput, SearchPaletteResponse } from "./palette-search-types.js";
39
32
  /** The client's own request primitives, handed down so this file owns no transport. */
40
33
  export type ColoursTransport = {
41
34
  get<T>(operationId: SdkOperationId, query?: QueryParams, signal?: AbortSignal): Promise<T>;
@@ -44,64 +37,11 @@ export type ColoursTransport = {
44
37
  export declare class ColoursClient {
45
38
  #private;
46
39
  constructor(transport: ColoursTransport);
47
- /**
48
- * THE QUESTION: "what colours does this CATEGORY come in?" — the representative
49
- * palette of 1–8 categories in ONE call (GET `/api/v1/colours/profiles`).
50
- *
51
- * An aggregate over a category, not a list of materials: use `mg.byColour()` or
52
- * `mg.search()` when you want the materials themselves, and
53
- * {@link ColoursClient.stats} for coverage across the whole corpus rather than
54
- * one category's palette. Check `basis` — this reads a scheduled materialized
55
- * view, so it can lag the catalog.
56
- *
57
- * `groupBy: "group"` takes product-type GROUP keys and merges every leaf type
58
- * under a group into one histogram BEFORE ranking — a group's palette, not the
59
- * union of its leaves' heads. `select: "diverse"` returns visually spread
60
- * swatches (greedy max-min over OKLab, 0.02 minimum separation, backfilled by
61
- * frequency if fewer survive) instead of the most frequent ones; a client
62
- * cannot recover that spread from a truncated frequency list, which is why it
63
- * is a server mode rather than a local sort.
64
- *
65
- * `groupBy: "merged"` merges across the KEYS as well and returns ONE profile
66
- * over the union of their leaves — the palette of a caller-defined facet such
67
- * as "Wood" (`wood_surface` + `wood_flooring`) that the taxonomy holds no
68
- * single node for, and that no combination of per-key responses reconstructs.
69
- * Its keys may sit at either tier, `categoryKey` is those keys sorted and
70
- * joined with `+`, and `members` hands them back so nothing has to split it.
71
- *
72
- * A selected category with no colour data comes back as a zero-count profile,
73
- * not a missing key.
74
- */
75
- profiles(params: ColourProfilesParams, signal?: AbortSignal): Promise<CategoryColourProfilesResponse>;
76
- /**
77
- * THE QUESTION: "how much colour does the graph hold AT ALL?" — corpus-level
78
- * coverage, before you trust any colour answer.
79
- *
80
- * The sanity check under every other colour call. Use
81
- * {@link ColoursClient.profiles} for one category's palette and
82
- * `mg.colourFacets()` for the vocabulary. `basis.omissions` lists what this
83
- * call deliberately does not answer — read it rather than assuming silence.
84
- *
85
- * Corpus colour coverage (GET `/api/v1/colours/stats`): how much of the graph
86
- * carries colour at all, the split across the canonical colour families, and
87
- * coverage per product-type group. `basis` states the source and freshness of
88
- * each section plus `basis.omissions` — what this call deliberately does not
89
- * answer, listed rather than silently absent.
90
- *
91
- * Pass `{ groupBy: "brand,category" }` for the whole brand x category x
92
- * family matrix in one call, or a `brandSlug`/`categoryKey` scope for a slice
93
- * of it — either way it arrives as `rows`, each row carrying `brandId` and
94
- * `brandSlug` to join on. Without any of those parameters the payload is the
95
- * platform-wide one and `rows` is absent. `families` is never scoped by them.
96
- */
97
- stats(params?: ColourStatsParams, signal?: AbortSignal): Promise<ColourStatsResponse>;
98
40
  /**
99
41
  * THE QUESTION: "what goes with this WHOLE PALETTE?" — N colours at once, at
100
42
  * stated proportions, not one colour repeated (POST `/api/v1/search/palettes`).
101
43
  *
102
- * Use `mg.byColour()` for a single colour, {@link ColoursClient.gradient} when
103
- * the colours form an ordered TRANSITION rather than a set, and
104
- * `mg.coordinate()` when the scheme is made of materials rather than colours.
44
+ * Use `mg.byColour()` for a single colour.
105
45
  *
106
46
  * Each colour scores `sim × prop`: `sim` is OKLab closeness on the same
107
47
  * mapping `byColour` ranks with, and `prop` rewards a candidate whose own
@@ -127,17 +67,4 @@ export declare class ColoursClient {
127
67
  * });
128
68
  */
129
69
  palette(input: SearchPaletteInput, signal?: AbortSignal): Promise<SearchPaletteResponse>;
130
- /**
131
- * THE QUESTION: "what moves from this colour to that one?" — materials that
132
- * travel ALONG a transition, in order (POST `/api/v1/search/gradients`).
133
- *
134
- * Order is the whole point: use {@link ColoursClient.palette} when the colours
135
- * are a set rather than a path, and `mg.byColour()` for a single colour.
136
- *
137
- * Not a palette: the score is multiplied by `coverage`, the share of the path
138
- * the candidate actually spans, so a material owning only one end ranks below
139
- * one that travels the whole way, and a flat single-colour material is
140
- * rejected outright however close its hue sits to the path.
141
- */
142
- gradient(input: SearchGradientInput, signal?: AbortSignal): Promise<SearchGradientResponse>;
143
70
  }