conduyt 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,8 @@ conduyt deals list --pipeline <id> # list deals
37
37
  conduyt pipelines # list pipelines and stages
38
38
  conduyt search "acme corp" # search across the CRM
39
39
  conduyt insights summary # run an AI insight query
40
+ conduyt privacy export <id> # GDPR data-portability export (owner/admin)
41
+ conduyt privacy forget <id> --confirm FORGET # GDPR erasure — IRREVERSIBLE, owner only
40
42
  conduyt api GET /api/v1/companies # raw authenticated request (escape hatch)
41
43
  conduyt config show # show resolved config (key masked)
42
44
  ```
package/dist/client.js CHANGED
@@ -20,7 +20,14 @@ export class ConduytClient {
20
20
  const text = await res.text();
21
21
  const json = text ? safeParse(text) : null;
22
22
  if (!res.ok) {
23
- const msg = json?.error || res.statusText || `HTTP ${res.status}`;
23
+ // Preserve structured non-2xx bodies: several endpoints return the data
24
+ // a caller needs to proceed IN the error body (e.g. custom-field delete
25
+ // 409 carries valueCount/dependencyCount for the --confirm flow).
26
+ // Reducing those to statusText would strand the user.
27
+ const structured = json && typeof json === "object" && !json.error
28
+ ? JSON.stringify(json)
29
+ : null;
30
+ const msg = json?.error || structured || res.statusText || `HTTP ${res.status}`;
24
31
  throw new Error(`Conduyt API ${res.status}: ${msg}`);
25
32
  }
26
33
  return json;
@@ -37,6 +44,85 @@ export class ConduytClient {
37
44
  del(path) {
38
45
  return this.request("DELETE", path);
39
46
  }
47
+ // Stream `ai chat`'s text/event-stream response to stdout as clean answer
48
+ // text. The server frames SSE records as `data: {"text":"..."}\n\n`, ends
49
+ // with `data: [DONE]\n\n`, and reports in-stream failures as
50
+ // `data: {"error":"..."}\n\n` — so we parse events (buffering across chunk
51
+ // splits), print only the text deltas, stop cleanly on [DONE], and THROW on
52
+ // an error event so the command exits non-zero instead of "succeeding" with
53
+ // raw protocol noise.
54
+ async stream(method, path, body) {
55
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
56
+ const res = await fetch(url, {
57
+ method,
58
+ headers: {
59
+ Authorization: `Bearer ${this.apiKey}`,
60
+ "Content-Type": "application/json",
61
+ Accept: "text/event-stream",
62
+ },
63
+ body: body !== undefined ? JSON.stringify(body) : undefined,
64
+ });
65
+ if (!res.ok || !res.body) {
66
+ const text = await res.text();
67
+ const json = text ? safeParse(text) : null;
68
+ const msg = json?.error || res.statusText || `HTTP ${res.status}`;
69
+ throw new Error(`Conduyt API ${res.status}: ${msg}`);
70
+ }
71
+ const reader = res.body.getReader();
72
+ const decoder = new TextDecoder();
73
+ let buffer = "";
74
+ let wroteAny = false;
75
+ const handleEvent = (payload) => {
76
+ if (payload === "[DONE]")
77
+ return "done";
78
+ const parsed = safeParse(payload);
79
+ if (parsed && typeof parsed === "object") {
80
+ const rec = parsed;
81
+ if (typeof rec.error === "string" && rec.error) {
82
+ if (wroteAny)
83
+ process.stdout.write("\n");
84
+ throw new Error(`AI stream error: ${rec.error}`);
85
+ }
86
+ if (typeof rec.text === "string" && rec.text) {
87
+ process.stdout.write(rec.text);
88
+ wroteAny = true;
89
+ }
90
+ }
91
+ return undefined;
92
+ };
93
+ const drainBuffer = () => {
94
+ // SSE events are separated by a blank line; fields we care about are
95
+ // `data: <payload>` lines (multi-line data joins with \n per spec).
96
+ let sep;
97
+ while ((sep = buffer.indexOf("\n\n")) !== -1) {
98
+ const rawEvent = buffer.slice(0, sep);
99
+ buffer = buffer.slice(sep + 2);
100
+ const dataLines = rawEvent
101
+ .split("\n")
102
+ .filter((l) => l.startsWith("data:"))
103
+ .map((l) => l.slice(5).replace(/^ /, ""));
104
+ if (dataLines.length === 0)
105
+ continue;
106
+ if (handleEvent(dataLines.join("\n")) === "done")
107
+ return "done";
108
+ }
109
+ return undefined;
110
+ };
111
+ outer: for (;;) {
112
+ const { done, value } = await reader.read();
113
+ if (value) {
114
+ buffer += decoder.decode(value, { stream: true });
115
+ if (drainBuffer() === "done")
116
+ break outer;
117
+ }
118
+ if (done)
119
+ break;
120
+ }
121
+ buffer += decoder.decode();
122
+ drainBuffer();
123
+ if (wroteAny)
124
+ process.stdout.write("\n");
125
+ }
40
126
  }
41
127
  function safeParse(text) {
42
128
  try {