@hayodev/crystallize-mcp 0.1.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.
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/build/src/audit.d.ts +23 -0
- package/build/src/audit.d.ts.map +1 -0
- package/build/src/audit.js +38 -0
- package/build/src/audit.js.map +1 -0
- package/build/src/bin/crystallize-mcp.d.ts +12 -0
- package/build/src/bin/crystallize-mcp.d.ts.map +1 -0
- package/build/src/bin/crystallize-mcp.js +45 -0
- package/build/src/bin/crystallize-mcp.js.map +1 -0
- package/build/src/bin/setup.d.ts +12 -0
- package/build/src/bin/setup.d.ts.map +1 -0
- package/build/src/bin/setup.js +171 -0
- package/build/src/bin/setup.js.map +1 -0
- package/build/src/client.d.ts +28 -0
- package/build/src/client.d.ts.map +1 -0
- package/build/src/client.js +124 -0
- package/build/src/client.js.map +1 -0
- package/build/src/credentials.d.ts +25 -0
- package/build/src/credentials.d.ts.map +1 -0
- package/build/src/credentials.js +104 -0
- package/build/src/credentials.js.map +1 -0
- package/build/src/errors.d.ts +12 -0
- package/build/src/errors.d.ts.map +1 -0
- package/build/src/errors.js +66 -0
- package/build/src/errors.js.map +1 -0
- package/build/src/index.d.ts +12 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +83 -0
- package/build/src/index.js.map +1 -0
- package/build/src/pii.d.ts +20 -0
- package/build/src/pii.d.ts.map +1 -0
- package/build/src/pii.js +85 -0
- package/build/src/pii.js.map +1 -0
- package/build/src/tools/catalogue.d.ts +7 -0
- package/build/src/tools/catalogue.d.ts.map +1 -0
- package/build/src/tools/catalogue.js +329 -0
- package/build/src/tools/catalogue.js.map +1 -0
- package/build/src/tools/customers.d.ts +7 -0
- package/build/src/tools/customers.d.ts.map +1 -0
- package/build/src/tools/customers.js +278 -0
- package/build/src/tools/customers.js.map +1 -0
- package/build/src/tools/discovery.d.ts +10 -0
- package/build/src/tools/discovery.d.ts.map +1 -0
- package/build/src/tools/discovery.js +285 -0
- package/build/src/tools/discovery.js.map +1 -0
- package/build/src/tools/orders.d.ts +7 -0
- package/build/src/tools/orders.d.ts.map +1 -0
- package/build/src/tools/orders.js +250 -0
- package/build/src/tools/orders.js.map +1 -0
- package/build/src/tools/shapes.d.ts +7 -0
- package/build/src/tools/shapes.d.ts.map +1 -0
- package/build/src/tools/shapes.js +194 -0
- package/build/src/tools/shapes.js.map +1 -0
- package/build/src/types.d.ts +43 -0
- package/build/src/types.d.ts.map +1 -0
- package/build/src/types.js +5 -0
- package/build/src/types.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crystallize API client wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Thin layer over @crystallize/js-api-client that adds:
|
|
5
|
+
* - Deep link generation to the Crystallize UI
|
|
6
|
+
* - Consistent config from env vars
|
|
7
|
+
*/
|
|
8
|
+
import type { ClientInterface } from '@crystallize/js-api-client';
|
|
9
|
+
import type { CrystallizeConfig } from './types.js';
|
|
10
|
+
export declare class CrystallizeClient {
|
|
11
|
+
readonly api: ClientInterface;
|
|
12
|
+
readonly config: CrystallizeConfig;
|
|
13
|
+
constructor(config: CrystallizeConfig);
|
|
14
|
+
/** Generate a deep link to an item in the Crystallize UI. */
|
|
15
|
+
itemLink(itemId: string, type?: string, language?: string): string;
|
|
16
|
+
/** Generate a deep link to a shape in the Crystallize UI. */
|
|
17
|
+
shapeLink(shapeIdentifier: string, language?: string): string;
|
|
18
|
+
/** Generate a deep link to an order in the Crystallize UI. */
|
|
19
|
+
orderLink(orderId: string, language?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Build config from env vars, falling back to OS keychain for credentials.
|
|
22
|
+
* Use this in the server binary. Use fromEnv() in tests.
|
|
23
|
+
*/
|
|
24
|
+
static fromEnvOrKeychain(): Promise<CrystallizeClient>;
|
|
25
|
+
/** Build config from environment variables only. */
|
|
26
|
+
static fromEnv(): CrystallizeClient;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAIlE,OAAO,KAAK,EAAE,iBAAiB,EAAW,MAAM,YAAY,CAAC;AAgB7D,qBAAa,iBAAiB;IAC5B,SAAgB,GAAG,EAAE,eAAe,CAAC;IACrC,SAAgB,MAAM,EAAE,iBAAiB,CAAC;gBAE9B,MAAM,EAAE,iBAAiB;IAWrC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,SAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAKtE,6DAA6D;IAC7D,SAAS,CAAC,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAK7D,8DAA8D;IAC9D,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAKrD;;;OAGG;WACU,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,CAAC;IA4D5D,oDAAoD;IACpD,MAAM,CAAC,OAAO,IAAI,iBAAiB;CA8BpC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crystallize API client wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Thin layer over @crystallize/js-api-client that adds:
|
|
5
|
+
* - Deep link generation to the Crystallize UI
|
|
6
|
+
* - Consistent config from env vars
|
|
7
|
+
*/
|
|
8
|
+
import { createClient } from '@crystallize/js-api-client';
|
|
9
|
+
import { readCredentials } from './credentials.js';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
function expandPath(p) {
|
|
13
|
+
return p.startsWith('~/') ? join(homedir(), p.slice(2)) : p;
|
|
14
|
+
}
|
|
15
|
+
function parsePiiMode() {
|
|
16
|
+
const raw = process.env.CRYSTALLIZE_PII_MODE ?? 'full';
|
|
17
|
+
if (!['full', 'masked', 'none'].includes(raw)) {
|
|
18
|
+
throw new Error(`Invalid CRYSTALLIZE_PII_MODE: "${raw}". Must be "full", "masked", or "none".`);
|
|
19
|
+
}
|
|
20
|
+
return raw;
|
|
21
|
+
}
|
|
22
|
+
export class CrystallizeClient {
|
|
23
|
+
api;
|
|
24
|
+
config;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
this.config = { piiMode: 'full', ...config };
|
|
27
|
+
this.api = createClient({
|
|
28
|
+
tenantIdentifier: config.tenantIdentifier,
|
|
29
|
+
tenantId: config.tenantId,
|
|
30
|
+
accessTokenId: config.accessTokenId,
|
|
31
|
+
accessTokenSecret: config.accessTokenSecret,
|
|
32
|
+
staticAuthToken: config.staticAuthToken,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Generate a deep link to an item in the Crystallize UI. */
|
|
36
|
+
itemLink(itemId, type = 'document', language) {
|
|
37
|
+
const lang = language ?? this.config.defaultLanguage ?? 'en';
|
|
38
|
+
return `https://app.crystallize.com/@${this.config.tenantIdentifier}/${lang}/catalogue/${type}/${itemId}`;
|
|
39
|
+
}
|
|
40
|
+
/** Generate a deep link to a shape in the Crystallize UI. */
|
|
41
|
+
shapeLink(shapeIdentifier, language) {
|
|
42
|
+
const lang = language ?? this.config.defaultLanguage ?? 'en';
|
|
43
|
+
return `https://app.crystallize.com/@${this.config.tenantIdentifier}/${lang}/settings/shapes/${shapeIdentifier}`;
|
|
44
|
+
}
|
|
45
|
+
/** Generate a deep link to an order in the Crystallize UI. */
|
|
46
|
+
orderLink(orderId, language) {
|
|
47
|
+
const lang = language ?? this.config.defaultLanguage ?? 'en';
|
|
48
|
+
return `https://app.crystallize.com/@${this.config.tenantIdentifier}/${lang}/orders/${orderId}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build config from env vars, falling back to OS keychain for credentials.
|
|
52
|
+
* Use this in the server binary. Use fromEnv() in tests.
|
|
53
|
+
*/
|
|
54
|
+
static async fromEnvOrKeychain() {
|
|
55
|
+
const tenantIdentifier = process.env.CRYSTALLIZE_TENANT_IDENTIFIER;
|
|
56
|
+
if (!tenantIdentifier) {
|
|
57
|
+
throw new Error('CRYSTALLIZE_TENANT_IDENTIFIER is required.\n' +
|
|
58
|
+
'Set it in your environment or MCP client config.');
|
|
59
|
+
}
|
|
60
|
+
const accessMode = (process.env.CRYSTALLIZE_ACCESS_MODE ??
|
|
61
|
+
'read');
|
|
62
|
+
if (!['read', 'write', 'admin'].includes(accessMode)) {
|
|
63
|
+
throw new Error(`Invalid CRYSTALLIZE_ACCESS_MODE: "${accessMode}". Must be "read", "write", or "admin".`);
|
|
64
|
+
}
|
|
65
|
+
// Env vars take priority; fall back to keychain for secrets only
|
|
66
|
+
let accessTokenId = process.env.CRYSTALLIZE_ACCESS_TOKEN_ID;
|
|
67
|
+
let accessTokenSecret = process.env.CRYSTALLIZE_ACCESS_TOKEN_SECRET;
|
|
68
|
+
let staticAuthToken = process.env.CRYSTALLIZE_STATIC_AUTH_TOKEN;
|
|
69
|
+
if (!accessTokenId && !staticAuthToken) {
|
|
70
|
+
const stored = await readCredentials();
|
|
71
|
+
accessTokenId = stored.accessTokenId;
|
|
72
|
+
accessTokenSecret = stored.accessTokenSecret;
|
|
73
|
+
staticAuthToken = stored.staticAuthToken;
|
|
74
|
+
}
|
|
75
|
+
const client = new CrystallizeClient({
|
|
76
|
+
tenantIdentifier,
|
|
77
|
+
tenantId: process.env.CRYSTALLIZE_TENANT_ID,
|
|
78
|
+
accessTokenId,
|
|
79
|
+
accessTokenSecret,
|
|
80
|
+
staticAuthToken,
|
|
81
|
+
accessMode,
|
|
82
|
+
piiMode: parsePiiMode(),
|
|
83
|
+
auditLog: process.env.CRYSTALLIZE_AUDIT_LOG
|
|
84
|
+
? expandPath(process.env.CRYSTALLIZE_AUDIT_LOG)
|
|
85
|
+
: undefined,
|
|
86
|
+
});
|
|
87
|
+
// Bootstrap tenant ID and default language from PIM API
|
|
88
|
+
if (!client.config.tenantId || !client.config.defaultLanguage) {
|
|
89
|
+
const data = (await client.api.pimApi(`query GetTenantMeta($identifier: String!) {
|
|
90
|
+
tenant { get(identifier: $identifier) { id defaults { language } } }
|
|
91
|
+
}`, { identifier: tenantIdentifier }));
|
|
92
|
+
client.config.tenantId ??= data.tenant.get.id;
|
|
93
|
+
client.config.defaultLanguage ??=
|
|
94
|
+
data.tenant.get.defaults?.language ?? 'en';
|
|
95
|
+
}
|
|
96
|
+
return client;
|
|
97
|
+
}
|
|
98
|
+
/** Build config from environment variables only. */
|
|
99
|
+
static fromEnv() {
|
|
100
|
+
const tenantIdentifier = process.env.CRYSTALLIZE_TENANT_IDENTIFIER;
|
|
101
|
+
if (!tenantIdentifier) {
|
|
102
|
+
throw new Error('CRYSTALLIZE_TENANT_IDENTIFIER is required.\n' +
|
|
103
|
+
'Set it in your environment or MCP client config.');
|
|
104
|
+
}
|
|
105
|
+
const accessMode = (process.env.CRYSTALLIZE_ACCESS_MODE ??
|
|
106
|
+
'read');
|
|
107
|
+
if (!['read', 'write', 'admin'].includes(accessMode)) {
|
|
108
|
+
throw new Error(`Invalid CRYSTALLIZE_ACCESS_MODE: "${accessMode}". Must be "read", "write", or "admin".`);
|
|
109
|
+
}
|
|
110
|
+
return new CrystallizeClient({
|
|
111
|
+
tenantIdentifier,
|
|
112
|
+
tenantId: process.env.CRYSTALLIZE_TENANT_ID,
|
|
113
|
+
accessTokenId: process.env.CRYSTALLIZE_ACCESS_TOKEN_ID,
|
|
114
|
+
accessTokenSecret: process.env.CRYSTALLIZE_ACCESS_TOKEN_SECRET,
|
|
115
|
+
staticAuthToken: process.env.CRYSTALLIZE_STATIC_AUTH_TOKEN,
|
|
116
|
+
accessMode,
|
|
117
|
+
piiMode: parsePiiMode(),
|
|
118
|
+
auditLog: process.env.CRYSTALLIZE_AUDIT_LOG
|
|
119
|
+
? expandPath(process.env.CRYSTALLIZE_AUDIT_LOG)
|
|
120
|
+
: undefined,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for crystallize-mcp.
|
|
3
|
+
*
|
|
4
|
+
* Provides keychain-backed credential storage via keytar (optional dependency).
|
|
5
|
+
* Falls back gracefully if keytar is unavailable (no keyring daemon, native
|
|
6
|
+
* binding mismatch, headless Linux, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Storage keys:
|
|
9
|
+
* service: crystallize-mcp
|
|
10
|
+
* accounts: access-token-id, access-token-secret, static-auth-token
|
|
11
|
+
*/
|
|
12
|
+
export interface StoredCredentials {
|
|
13
|
+
accessTokenId?: string;
|
|
14
|
+
accessTokenSecret?: string;
|
|
15
|
+
staticAuthToken?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Read stored credentials from the OS keychain. Returns empty object if keytar unavailable. */
|
|
18
|
+
export declare function readCredentials(): Promise<StoredCredentials>;
|
|
19
|
+
/** Store credentials in the OS keychain. Throws if keytar is unavailable. */
|
|
20
|
+
export declare function writeCredentials(creds: StoredCredentials): Promise<void>;
|
|
21
|
+
/** Remove all stored credentials from the OS keychain. */
|
|
22
|
+
export declare function deleteCredentials(): Promise<void>;
|
|
23
|
+
/** True if keytar loaded successfully and can reach the OS keyring. */
|
|
24
|
+
export declare function isKeychainAvailable(): Promise<boolean>;
|
|
25
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA8BD,gGAAgG;AAChG,wBAAsB,eAAe,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAuBlE;AAED,6EAA6E;AAC7E,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,0DAA0D;AAC1D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAevD;AAED,uEAAuE;AACvE,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC,CAY5D"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage for crystallize-mcp.
|
|
3
|
+
*
|
|
4
|
+
* Provides keychain-backed credential storage via keytar (optional dependency).
|
|
5
|
+
* Falls back gracefully if keytar is unavailable (no keyring daemon, native
|
|
6
|
+
* binding mismatch, headless Linux, etc.).
|
|
7
|
+
*
|
|
8
|
+
* Storage keys:
|
|
9
|
+
* service: crystallize-mcp
|
|
10
|
+
* accounts: access-token-id, access-token-secret, static-auth-token
|
|
11
|
+
*/
|
|
12
|
+
const SERVICE = 'crystallize-mcp';
|
|
13
|
+
/** Attempt to load @napi-rs/keyring keytar compat shim. Returns null if unavailable. */
|
|
14
|
+
async function loadKeytar() {
|
|
15
|
+
try {
|
|
16
|
+
// Use the keytar-compatible shim — same API, Rust/NAPI-RS backend.
|
|
17
|
+
// Dynamic import with unknown cast: @napi-rs/keyring is an optional dep,
|
|
18
|
+
// so TypeScript has no types for it at compile time.
|
|
19
|
+
const mod = (await import('@napi-rs/keyring/keytar.js'));
|
|
20
|
+
if (typeof mod.getPassword !== 'function') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return mod;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Read stored credentials from the OS keychain. Returns empty object if keytar unavailable. */
|
|
30
|
+
export async function readCredentials() {
|
|
31
|
+
const kt = await loadKeytar();
|
|
32
|
+
if (!kt) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const [accessTokenId, accessTokenSecret, staticAuthToken] = await Promise.all([
|
|
37
|
+
kt.getPassword(SERVICE, 'access-token-id'),
|
|
38
|
+
kt.getPassword(SERVICE, 'access-token-secret'),
|
|
39
|
+
kt.getPassword(SERVICE, 'static-auth-token'),
|
|
40
|
+
]);
|
|
41
|
+
return {
|
|
42
|
+
accessTokenId: accessTokenId ?? undefined,
|
|
43
|
+
accessTokenSecret: accessTokenSecret ?? undefined,
|
|
44
|
+
staticAuthToken: staticAuthToken ?? undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Keyring daemon not running, permission denied, etc.
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/** Store credentials in the OS keychain. Throws if keytar is unavailable. */
|
|
53
|
+
export async function writeCredentials(creds) {
|
|
54
|
+
const kt = await loadKeytar();
|
|
55
|
+
if (!kt) {
|
|
56
|
+
throw new Error('keytar is not available on this system.\n' +
|
|
57
|
+
'Install it with: npm install -g keytar\n' +
|
|
58
|
+
'Or set credentials as environment variables instead.');
|
|
59
|
+
}
|
|
60
|
+
const writes = [];
|
|
61
|
+
if (creds.accessTokenId) {
|
|
62
|
+
writes.push(kt.setPassword(SERVICE, 'access-token-id', creds.accessTokenId));
|
|
63
|
+
}
|
|
64
|
+
if (creds.accessTokenSecret) {
|
|
65
|
+
writes.push(kt.setPassword(SERVICE, 'access-token-secret', creds.accessTokenSecret));
|
|
66
|
+
}
|
|
67
|
+
if (creds.staticAuthToken) {
|
|
68
|
+
writes.push(kt.setPassword(SERVICE, 'static-auth-token', creds.staticAuthToken));
|
|
69
|
+
}
|
|
70
|
+
await Promise.all(writes);
|
|
71
|
+
}
|
|
72
|
+
/** Remove all stored credentials from the OS keychain. */
|
|
73
|
+
export async function deleteCredentials() {
|
|
74
|
+
const kt = await loadKeytar();
|
|
75
|
+
if (!kt) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await Promise.all([
|
|
80
|
+
kt.deletePassword(SERVICE, 'access-token-id'),
|
|
81
|
+
kt.deletePassword(SERVICE, 'access-token-secret'),
|
|
82
|
+
kt.deletePassword(SERVICE, 'static-auth-token'),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// best-effort
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** True if keytar loaded successfully and can reach the OS keyring. */
|
|
90
|
+
export async function isKeychainAvailable() {
|
|
91
|
+
const kt = await loadKeytar();
|
|
92
|
+
if (!kt) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
// Probe with a read — cheap and non-destructive
|
|
97
|
+
await kt.getPassword(SERVICE, '_probe');
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Categorized error handling for actionable error messages.
|
|
3
|
+
*/
|
|
4
|
+
import type { ErrorCategory } from './types.js';
|
|
5
|
+
export declare class CrystallizeToolError extends Error {
|
|
6
|
+
readonly category: ErrorCategory;
|
|
7
|
+
readonly hint?: string | undefined;
|
|
8
|
+
constructor(message: string, category: ErrorCategory, hint?: string | undefined);
|
|
9
|
+
}
|
|
10
|
+
/** Format an error into a user-friendly message with actionable hints. */
|
|
11
|
+
export declare function formatError(error: unknown): string;
|
|
12
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,oBAAqB,SAAQ,KAAK;aAG3B,QAAQ,EAAE,aAAa;aACvB,IAAI,CAAC,EAAE,MAAM;gBAF7B,OAAO,EAAE,MAAM,EACC,QAAQ,EAAE,aAAa,EACvB,IAAI,CAAC,EAAE,MAAM,YAAA;CAKhC;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA0DlD"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Categorized error handling for actionable error messages.
|
|
3
|
+
*/
|
|
4
|
+
export class CrystallizeToolError extends Error {
|
|
5
|
+
category;
|
|
6
|
+
hint;
|
|
7
|
+
constructor(message, category, hint) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.category = category;
|
|
10
|
+
this.hint = hint;
|
|
11
|
+
this.name = 'CrystallizeToolError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Format an error into a user-friendly message with actionable hints. */
|
|
15
|
+
export function formatError(error) {
|
|
16
|
+
if (error instanceof CrystallizeToolError) {
|
|
17
|
+
const parts = [`Error: ${error.message}`];
|
|
18
|
+
if (error.hint) {
|
|
19
|
+
parts.push(`Hint: ${error.hint}`);
|
|
20
|
+
}
|
|
21
|
+
return parts.join('\n');
|
|
22
|
+
}
|
|
23
|
+
let message;
|
|
24
|
+
if (error instanceof Error) {
|
|
25
|
+
message = error.message;
|
|
26
|
+
}
|
|
27
|
+
else if (typeof error === 'object' && error !== null) {
|
|
28
|
+
const obj = error;
|
|
29
|
+
// GraphQL errors array: { errors: [{ message: "..." }] }
|
|
30
|
+
if (Array.isArray(obj.errors) &&
|
|
31
|
+
obj.errors.length > 0 &&
|
|
32
|
+
typeof obj.errors[0].message === 'string') {
|
|
33
|
+
message = obj.errors
|
|
34
|
+
.map(e => e.message)
|
|
35
|
+
.join('; ');
|
|
36
|
+
}
|
|
37
|
+
else if ('message' in obj) {
|
|
38
|
+
message = String(obj.message);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
message = JSON.stringify(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
message = String(error);
|
|
46
|
+
}
|
|
47
|
+
// Detect common API error patterns and add hints
|
|
48
|
+
if (message.includes('401') ||
|
|
49
|
+
message.includes('Unauthorized') ||
|
|
50
|
+
message.includes('authentication')) {
|
|
51
|
+
return `Error: Authentication failed.\nHint: Check CRYSTALLIZE_ACCESS_TOKEN_ID and CRYSTALLIZE_ACCESS_TOKEN_SECRET env vars.`;
|
|
52
|
+
}
|
|
53
|
+
if (message.includes('403') || message.includes('Forbidden')) {
|
|
54
|
+
return `Error: Access denied.\nHint: Your token may lack the required permissions, or set CRYSTALLIZE_ACCESS_MODE=write for write operations.`;
|
|
55
|
+
}
|
|
56
|
+
if (message.includes('429') || message.includes('rate limit')) {
|
|
57
|
+
return `Error: API rate limited.\nHint: Wait a moment and retry.`;
|
|
58
|
+
}
|
|
59
|
+
if (message.includes('404') ||
|
|
60
|
+
message.includes('not found') ||
|
|
61
|
+
message.includes('Not Found')) {
|
|
62
|
+
return `Error: ${message}\nHint: Check the path or identifier — use browse_catalogue or list_shapes to explore what's available.`;
|
|
63
|
+
}
|
|
64
|
+
return `Error: ${message}`;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAG3B;IACA;IAHlB,YACE,OAAe,EACC,QAAuB,EACvB,IAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,aAAQ,GAAR,QAAQ,CAAe;QACvB,SAAI,GAAJ,IAAI,CAAS;QAG7B,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1C,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,OAAe,CAAC;IACpB,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC1B,CAAC;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACvD,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,yDAAyD;QACzD,IACE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YACzB,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YACrB,OAAQ,GAAG,CAAC,MAAM,CAAC,CAAC,CAA6B,CAAC,OAAO,KAAK,QAAQ,EACtE,CAAC;YACD,OAAO,GAAI,GAAG,CAAC,MAAgC;iBAC5C,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;iBACnB,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;aAAM,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;YAC5B,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,iDAAiD;IACjD,IACE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;QAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAClC,CAAC;QACD,OAAO,sHAAsH,CAAC;IAChI,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7D,OAAO,uIAAuI,CAAC;IACjJ,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9D,OAAO,0DAA0D,CAAC;IACpE,CAAC;IAED,IACE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;QAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAC7B,CAAC;QACD,OAAO,UAAU,OAAO,yGAAyG,CAAC;IACpI,CAAC;IAED,OAAO,UAAU,OAAO,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crystallize MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Provides headless commerce tools for AI agents via the Model Context Protocol.
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { CrystallizeClient } from './client.js';
|
|
8
|
+
export declare function createCrystallizeMcpServer(client?: CrystallizeClient): {
|
|
9
|
+
server: McpServer;
|
|
10
|
+
client: CrystallizeClient;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAuBhD,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,iBAAiB,GAAG;IACtE,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAsEA"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crystallize MCP Server.
|
|
3
|
+
*
|
|
4
|
+
* Provides headless commerce tools for AI agents via the Model Context Protocol.
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
|
+
import { CrystallizeClient } from './client.js';
|
|
8
|
+
import { formatError } from './errors.js';
|
|
9
|
+
import { AuditLogger, summariseResult } from './audit.js';
|
|
10
|
+
// Tool groups
|
|
11
|
+
import { catalogueTools } from './tools/catalogue.js';
|
|
12
|
+
import { shapeTools } from './tools/shapes.js';
|
|
13
|
+
import { discoveryTools } from './tools/discovery.js';
|
|
14
|
+
import { orderTools } from './tools/orders.js';
|
|
15
|
+
import { customerTools } from './tools/customers.js';
|
|
16
|
+
/** Access mode hierarchy: read < write < admin. */
|
|
17
|
+
const ACCESS_LEVELS = {
|
|
18
|
+
read: 0,
|
|
19
|
+
write: 1,
|
|
20
|
+
admin: 2,
|
|
21
|
+
};
|
|
22
|
+
function hasAccess(required, current) {
|
|
23
|
+
return ACCESS_LEVELS[current] >= ACCESS_LEVELS[required];
|
|
24
|
+
}
|
|
25
|
+
export function createCrystallizeMcpServer(client) {
|
|
26
|
+
const crystallize = client ?? CrystallizeClient.fromEnv();
|
|
27
|
+
const server = new McpServer({
|
|
28
|
+
name: 'crystallize-mcp',
|
|
29
|
+
version: '0.1.0',
|
|
30
|
+
}, {
|
|
31
|
+
capabilities: {
|
|
32
|
+
tools: {},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
// Collect all tool definitions
|
|
36
|
+
const allTools = [
|
|
37
|
+
...catalogueTools(crystallize),
|
|
38
|
+
...shapeTools(crystallize),
|
|
39
|
+
...discoveryTools(crystallize),
|
|
40
|
+
...orderTools(crystallize),
|
|
41
|
+
...customerTools(crystallize),
|
|
42
|
+
];
|
|
43
|
+
// Audit logger — only active if CRYSTALLIZE_AUDIT_LOG is set
|
|
44
|
+
const audit = crystallize.config.auditLog
|
|
45
|
+
? new AuditLogger(crystallize.config.auditLog)
|
|
46
|
+
: null;
|
|
47
|
+
// Register tools that match the current access mode
|
|
48
|
+
for (const tool of allTools) {
|
|
49
|
+
const requiredMode = tool.mode ?? 'read';
|
|
50
|
+
if (!hasAccess(requiredMode, crystallize.config.accessMode)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
server.tool(tool.name, tool.description, tool.schema, async (params) => {
|
|
54
|
+
try {
|
|
55
|
+
const result = await tool.handler(params);
|
|
56
|
+
audit?.log({
|
|
57
|
+
ts: new Date().toISOString(),
|
|
58
|
+
tool: tool.name,
|
|
59
|
+
params,
|
|
60
|
+
result: summariseResult(result),
|
|
61
|
+
tenant: crystallize.config.tenantIdentifier,
|
|
62
|
+
});
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const errorResult = {
|
|
67
|
+
content: [{ type: 'text', text: formatError(error) }],
|
|
68
|
+
isError: true,
|
|
69
|
+
};
|
|
70
|
+
audit?.log({
|
|
71
|
+
ts: new Date().toISOString(),
|
|
72
|
+
tool: tool.name,
|
|
73
|
+
params,
|
|
74
|
+
result: 'error',
|
|
75
|
+
tenant: crystallize.config.tenantIdentifier,
|
|
76
|
+
});
|
|
77
|
+
return errorResult;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return { server, client: crystallize };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PII masking utilities.
|
|
3
|
+
*
|
|
4
|
+
* Masks personal data in customer and order responses based on the
|
|
5
|
+
* configured CRYSTALLIZE_PII_MODE.
|
|
6
|
+
*/
|
|
7
|
+
import type { PiiMode } from './types.js';
|
|
8
|
+
export type { PiiMode };
|
|
9
|
+
/** Mask an email: `hani@example.com` → `h***@example.com` */
|
|
10
|
+
export declare function maskEmail(email: string): string;
|
|
11
|
+
/** Mask a phone number: keeps only last 4 digits → `***-1264` */
|
|
12
|
+
export declare function maskPhone(phone: string): string;
|
|
13
|
+
/** Strip an address down to city + country only. */
|
|
14
|
+
export declare function maskAddress<T>(addr: T): T;
|
|
15
|
+
/**
|
|
16
|
+
* Apply PII masking to a flat object with known field names.
|
|
17
|
+
* Always returns a new object — never mutates the original.
|
|
18
|
+
*/
|
|
19
|
+
export declare function maskFields<T extends Record<string, unknown>>(obj: T, mode: PiiMode): T;
|
|
20
|
+
//# sourceMappingURL=pii.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii.d.ts","sourceRoot":"","sources":["../../src/pii.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,OAAO,EAAE,CAAC;AAExB,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C;AAED,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAgBzC;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1D,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,OAAO,GACZ,CAAC,CA6CH"}
|
package/build/src/pii.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PII masking utilities.
|
|
3
|
+
*
|
|
4
|
+
* Masks personal data in customer and order responses based on the
|
|
5
|
+
* configured CRYSTALLIZE_PII_MODE.
|
|
6
|
+
*/
|
|
7
|
+
/** Mask an email: `hani@example.com` → `h***@example.com` */
|
|
8
|
+
export function maskEmail(email) {
|
|
9
|
+
const [local, domain] = email.split('@');
|
|
10
|
+
if (!local || !domain) {
|
|
11
|
+
return '***';
|
|
12
|
+
}
|
|
13
|
+
return `${local[0]}***@${domain}`;
|
|
14
|
+
}
|
|
15
|
+
/** Mask a phone number: keeps only last 4 digits → `***-1264` */
|
|
16
|
+
export function maskPhone(phone) {
|
|
17
|
+
const digits = phone.replace(/\D/g, '');
|
|
18
|
+
if (digits.length < 4) {
|
|
19
|
+
return '***';
|
|
20
|
+
}
|
|
21
|
+
return `***-${digits.slice(-4)}`;
|
|
22
|
+
}
|
|
23
|
+
/** Strip an address down to city + country only. */
|
|
24
|
+
export function maskAddress(addr) {
|
|
25
|
+
const masked = { ...addr };
|
|
26
|
+
for (const key of [
|
|
27
|
+
'street',
|
|
28
|
+
'street2',
|
|
29
|
+
'streetNumber',
|
|
30
|
+
'postalCode',
|
|
31
|
+
'state',
|
|
32
|
+
'email',
|
|
33
|
+
'phone',
|
|
34
|
+
'firstName',
|
|
35
|
+
'lastName',
|
|
36
|
+
]) {
|
|
37
|
+
delete masked[key];
|
|
38
|
+
}
|
|
39
|
+
return masked;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Apply PII masking to a flat object with known field names.
|
|
43
|
+
* Always returns a new object — never mutates the original.
|
|
44
|
+
*/
|
|
45
|
+
export function maskFields(obj, mode) {
|
|
46
|
+
if (mode === 'full') {
|
|
47
|
+
return { ...obj };
|
|
48
|
+
}
|
|
49
|
+
const result = { ...obj };
|
|
50
|
+
if (mode === 'none') {
|
|
51
|
+
for (const key of [
|
|
52
|
+
'email',
|
|
53
|
+
'phone',
|
|
54
|
+
'companyName',
|
|
55
|
+
'taxNumber',
|
|
56
|
+
'birthDate',
|
|
57
|
+
]) {
|
|
58
|
+
if (key in result) {
|
|
59
|
+
delete result[key];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if ('addresses' in result) {
|
|
63
|
+
delete result.addresses;
|
|
64
|
+
}
|
|
65
|
+
if ('firstName' in result) {
|
|
66
|
+
delete result.firstName;
|
|
67
|
+
}
|
|
68
|
+
if ('lastName' in result) {
|
|
69
|
+
delete result.lastName;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
// mode === 'masked'
|
|
74
|
+
if (typeof result.email === 'string') {
|
|
75
|
+
result.email = maskEmail(result.email);
|
|
76
|
+
}
|
|
77
|
+
if (typeof result.phone === 'string') {
|
|
78
|
+
result.phone = maskPhone(result.phone);
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(result.addresses)) {
|
|
81
|
+
result.addresses = result.addresses.map((addr) => maskAddress(addr));
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=pii.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii.js","sourceRoot":"","sources":["../../src/pii.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,6DAA6D;AAC7D,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACnC,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,WAAW,CAAI,IAAO;IACpC,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAA6B,CAAC;IACtD,KAAK,MAAM,GAAG,IAAI;QAChB,QAAQ;QACR,SAAS;QACT,cAAc;QACd,YAAY;QACZ,OAAO;QACP,OAAO;QACP,OAAO;QACP,WAAW;QACX,UAAU;KACX,EAAE,CAAC;QACF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,MAAW,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,GAAM,EACN,IAAa;IAEb,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,GAAG,GAAG,EAAE,CAAC;IACpB,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;IAE1B,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,KAAK,MAAM,GAAG,IAAI;YAChB,OAAO;YACP,OAAO;YACP,aAAa;YACb,WAAW;YACX,WAAW;SACH,EAAE,CAAC;YACX,IAAI,GAAG,IAAI,MAAM,EAAE,CAAC;gBAClB,OAAQ,MAAkC,CAAC,GAAG,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QACD,IAAI,WAAW,IAAI,MAAM,EAAE,CAAC;YAC1B,OAAQ,MAAkC,CAAC,SAAS,CAAC;QACvD,CAAC;QACD,IAAI,WAAW,IAAI,MAAM,EAAE,CAAC;YAC1B,OAAQ,MAAkC,CAAC,SAAS,CAAC;QACvD,CAAC;QACD,IAAI,UAAU,IAAI,MAAM,EAAE,CAAC;YACzB,OAAQ,MAAkC,CAAC,QAAQ,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,oBAAoB;IACpB,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAkC,CAAC,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAkC,CAAC,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACnC,MAAkC,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,CAClE,CAAC,IAA6B,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CACrD,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalogue tools — browse, get items, search, and product variants.
|
|
3
|
+
*/
|
|
4
|
+
import type { CrystallizeClient } from '../client.js';
|
|
5
|
+
import type { ToolDefinition } from '../types.js';
|
|
6
|
+
export declare function catalogueTools(client: CrystallizeClient): ToolDefinition[];
|
|
7
|
+
//# sourceMappingURL=catalogue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalogue.d.ts","sourceRoot":"","sources":["../../../src/tools/catalogue.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAiT1E"}
|