@dayofweek/dcli 1.4.0 → 1.6.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/dist/client.d.ts CHANGED
@@ -2,6 +2,11 @@
2
2
  * HTTP client for the Day of Week platform REST API.
3
3
  * All calls go through the proxy at field.dayofweek.com/app/api/dcli.
4
4
  */
5
+ /**
6
+ * Node's Buffer is a Uint8Array view over a possibly-larger, possibly-shared
7
+ * backing store, which is not assignable to BodyInit. Copy out the exact bytes.
8
+ */
9
+ export declare function toArrayBuffer(view: Uint8Array): ArrayBuffer;
5
10
  export declare class ApiError extends Error {
6
11
  status: number;
7
12
  code?: string | undefined;
@@ -103,23 +108,27 @@ export declare class DayOfWeekClient {
103
108
  type?: string;
104
109
  parent?: string;
105
110
  limit?: number;
111
+ org?: string;
106
112
  }): Promise<any[]>;
107
- getEntity(entityId: string): Promise<any>;
113
+ getEntity(entityId: string, org?: string): Promise<any>;
108
114
  listProduce(opts?: {
109
115
  entity?: string;
110
116
  limit?: number;
117
+ org?: string;
111
118
  }): Promise<any[]>;
112
119
  listContacts(opts?: {
113
120
  entity?: string;
114
121
  limit?: number;
122
+ org?: string;
115
123
  }): Promise<any[]>;
116
- listEntityTypes(): Promise<any[]>;
124
+ listEntityTypes(org?: string): Promise<any[]>;
117
125
  searchCatalog(opts?: {
118
126
  search?: string;
119
127
  parent?: string;
120
128
  type?: string;
121
129
  includeCategories?: boolean;
122
130
  limit?: number;
131
+ org?: string;
123
132
  }): Promise<any[]>;
124
133
  listProposals(opts?: {
125
134
  status?: string;
@@ -154,6 +163,161 @@ export declare class DayOfWeekClient {
154
163
  truncated: boolean;
155
164
  total: number;
156
165
  }>;
166
+ /**
167
+ * Direct produce entry — creates real Creator rows, not proposals.
168
+ *
169
+ * With `dryRun` the server runs the whole import and rolls it back, so the
170
+ * operator approves the server's verdict instead of a reconstruction of it.
171
+ */
172
+ importProduce(input: {
173
+ entityId: string;
174
+ items: unknown[];
175
+ dryRun?: boolean;
176
+ skipExistingNames?: boolean;
177
+ org?: string;
178
+ }): Promise<any>;
179
+ /** Recipe ingredients still standing in for something vaguer. */
180
+ listImpreciseIngredients(entityId: string, org?: string): Promise<any>;
181
+ /** Point an imprecise ingredient at what it actually is. */
182
+ refineIngredient(input: {
183
+ processInputId: string;
184
+ catalogConceptId?: string;
185
+ materialId?: string;
186
+ role?: string;
187
+ qty?: number;
188
+ unitCode?: string;
189
+ stillImprecise?: boolean;
190
+ org?: string;
191
+ }): Promise<any>;
192
+ /**
193
+ * Grow the shared produce catalog. Admin only — every customer sees these
194
+ * concepts, so adding one is a platform decision, not a per-import shortcut.
195
+ */
196
+ createCatalogConcepts(input: {
197
+ concepts: unknown[];
198
+ org?: string;
199
+ }): Promise<any>;
200
+ /** Area sources, metadata only, newest first — the freshness evidence. */
201
+ listBrainSources(areaId: string): Promise<any>;
202
+ /** Entities with an active customer role and no investor/partner/producer role. */
203
+ adminCustomersOnly(): Promise<any>;
204
+ /** Tips mailed to press@mail.dayofweek.com, read by the press-scan skill. */
205
+ adminPressInbox(opts?: {
206
+ since?: string;
207
+ limit?: number;
208
+ }): Promise<any>;
209
+ /**
210
+ * List the knowledge documents attached to an entity. `full` returns each
211
+ * document's whole content instead of an excerpt, which is what you want when
212
+ * mirroring an entity's sources into an external knowledge base.
213
+ */
214
+ listKnowledge(opts: {
215
+ entity: string;
216
+ full?: boolean;
217
+ org?: string;
218
+ }): Promise<any[]>;
219
+ getKnowledge(documentId: string, org?: string): Promise<any>;
220
+ /** Semantic search across knowledge documents. */
221
+ searchKnowledge(opts: {
222
+ query: string;
223
+ entity?: string;
224
+ types?: string[];
225
+ limit?: number;
226
+ allOrgs?: boolean;
227
+ org?: string;
228
+ }): Promise<any>;
229
+ /**
230
+ * Add a markdown knowledge note.
231
+ *
232
+ * Without `direct` this submits a proposal for human review — the default,
233
+ * and the only option non-admin tokens have. With `direct: true` an admin
234
+ * token writes the document immediately, skipping review. Ask the operator
235
+ * before doing that; see references/admin.md in the served skill.
236
+ */
237
+ addKnowledge(input: {
238
+ entityId: string;
239
+ title: string;
240
+ content: string;
241
+ sourceType?: string;
242
+ sourceUrl?: string;
243
+ sourceDescription?: string;
244
+ confidence?: number;
245
+ sourceAgent?: string;
246
+ direct?: boolean;
247
+ org?: string;
248
+ }): Promise<any>;
249
+ /**
250
+ * Attach a binary source (PDF, DOCX, XLSX, …) to an entity. Admin only.
251
+ *
252
+ * Three hops: ask for an upload URL, send the bytes straight to Convex
253
+ * storage, then register the resulting storageId. The bytes never pass
254
+ * through function arguments, so file size isn't bounded by an arg limit.
255
+ */
256
+ attachKnowledgeFile(input: {
257
+ entityId: string;
258
+ /** Raw file bytes. Buffer callers: pass `toArrayBuffer(buf)` below. */
259
+ data: ArrayBuffer;
260
+ fileName: string;
261
+ mimeType: string;
262
+ sourceType?: string;
263
+ sourceUrl?: string;
264
+ sourceDescription?: string;
265
+ org?: string;
266
+ }): Promise<any>;
267
+ listDatasets(org?: string): Promise<{
268
+ datasets: Array<{
269
+ name: string;
270
+ description: string;
271
+ params: string[];
272
+ }>;
273
+ }>;
274
+ readDataset(name: string, opts?: {
275
+ limit?: number;
276
+ org?: string;
277
+ }): Promise<any>;
278
+ listFeedback(opts?: {
279
+ status?: string;
280
+ priority?: string;
281
+ category?: string;
282
+ limit?: number;
283
+ }): Promise<{
284
+ items: any[];
285
+ }>;
286
+ getFeedbackItem(itemId: string): Promise<any>;
287
+ /** Top-N from the heuristic prioritizer — "what should I work on next?". */
288
+ feedbackRecommendations(limit?: number): Promise<{
289
+ recommendations: any[];
290
+ }>;
291
+ claimFeedbackItem(itemId: string): Promise<any>;
292
+ commentOnFeedbackItem(itemId: string, body: string): Promise<any>;
293
+ updateFeedbackItem(itemId: string, updates: {
294
+ status?: string;
295
+ priority?: string;
296
+ rejectedReason?: string;
297
+ }): Promise<any>;
298
+ listEmailThreads(opts?: {
299
+ status?: string;
300
+ since?: string;
301
+ customer?: string;
302
+ limit?: number;
303
+ org?: string;
304
+ }): Promise<any>;
305
+ getEmailThread(threadKey: string): Promise<any>;
306
+ replyToEmailThread(threadKey: string, input: {
307
+ message: string;
308
+ subject?: string;
309
+ to?: string;
310
+ cc?: string[];
311
+ quote?: boolean;
312
+ }): Promise<any>;
313
+ composeEmail(input: {
314
+ entityId?: string;
315
+ inbox?: string;
316
+ to: string;
317
+ cc?: string[];
318
+ subject: string;
319
+ message: string;
320
+ }): Promise<any>;
157
321
  getSkillBundle(name?: string): Promise<{
158
322
  name: string;
159
323
  version: string;
@@ -172,6 +336,7 @@ export declare class DayOfWeekClient {
172
336
  getSchema(): Promise<any>;
173
337
  private get;
174
338
  private post;
339
+ private put;
175
340
  private patch;
176
341
  private delete;
177
342
  private request;
package/dist/client.js CHANGED
@@ -8,6 +8,15 @@ import { basename, dirname, join } from "node:path";
8
8
  import { Readable, Transform } from "node:stream";
9
9
  import { pipeline } from "node:stream/promises";
10
10
  const DEFAULT_BASE_URL = "https://field.dayofweek.com/app/api/dcli";
11
+ /**
12
+ * Node's Buffer is a Uint8Array view over a possibly-larger, possibly-shared
13
+ * backing store, which is not assignable to BodyInit. Copy out the exact bytes.
14
+ */
15
+ export function toArrayBuffer(view) {
16
+ const out = new ArrayBuffer(view.byteLength);
17
+ new Uint8Array(out).set(view);
18
+ return out;
19
+ }
11
20
  export class ApiError extends Error {
12
21
  status;
13
22
  code;
@@ -180,6 +189,8 @@ export class DayOfWeekClient {
180
189
  return this.get(`/brain/audit?${query}`);
181
190
  }
182
191
  // ── Read ──────────────────────────────────────────────────────────────────
192
+ // Every read endpoint accepts ?org=<slug|id> for admin tokens. Exposing it as
193
+ // --org is what keeps admins from dropping to curl for cross-org reads.
183
194
  async listEntities(opts) {
184
195
  const params = new URLSearchParams();
185
196
  if (opts?.type)
@@ -188,11 +199,14 @@ export class DayOfWeekClient {
188
199
  params.set("parent", opts.parent);
189
200
  if (opts?.limit)
190
201
  params.set("limit", String(opts.limit));
202
+ if (opts?.org)
203
+ params.set("org", opts.org);
191
204
  const qs = params.toString();
192
205
  return this.get(`/entities${qs ? `?${qs}` : ""}`);
193
206
  }
194
- async getEntity(entityId) {
195
- return this.get(`/entities/${entityId}`);
207
+ async getEntity(entityId, org) {
208
+ const qs = org ? `?org=${encodeURIComponent(org)}` : "";
209
+ return this.get(`/entities/${entityId}${qs}`);
196
210
  }
197
211
  async listProduce(opts) {
198
212
  const params = new URLSearchParams();
@@ -200,6 +214,8 @@ export class DayOfWeekClient {
200
214
  params.set("entity", opts.entity);
201
215
  if (opts?.limit)
202
216
  params.set("limit", String(opts.limit));
217
+ if (opts?.org)
218
+ params.set("org", opts.org);
203
219
  const qs = params.toString();
204
220
  return this.get(`/produce${qs ? `?${qs}` : ""}`);
205
221
  }
@@ -209,11 +225,14 @@ export class DayOfWeekClient {
209
225
  params.set("entity", opts.entity);
210
226
  if (opts?.limit)
211
227
  params.set("limit", String(opts.limit));
228
+ if (opts?.org)
229
+ params.set("org", opts.org);
212
230
  const qs = params.toString();
213
231
  return this.get(`/contacts${qs ? `?${qs}` : ""}`);
214
232
  }
215
- async listEntityTypes() {
216
- return this.get("/entity-types");
233
+ async listEntityTypes(org) {
234
+ const qs = org ? `?org=${encodeURIComponent(org)}` : "";
235
+ return this.get(`/entity-types${qs}`);
217
236
  }
218
237
  async searchCatalog(opts) {
219
238
  const params = new URLSearchParams();
@@ -227,6 +246,8 @@ export class DayOfWeekClient {
227
246
  params.set("includeCategories", "true");
228
247
  if (opts?.limit)
229
248
  params.set("limit", String(opts.limit));
249
+ if (opts?.org)
250
+ params.set("org", opts.org);
230
251
  const qs = params.toString();
231
252
  return this.get(`/catalog${qs ? `?${qs}` : ""}`);
232
253
  }
@@ -279,6 +300,211 @@ export class DayOfWeekClient {
279
300
  const qs = params.toString();
280
301
  return this.get(`/admin/proposals${qs ? `?${qs}` : ""}`);
281
302
  }
303
+ // ── Produce import (admin) ────────────────────────────────────────────────
304
+ /**
305
+ * Direct produce entry — creates real Creator rows, not proposals.
306
+ *
307
+ * With `dryRun` the server runs the whole import and rolls it back, so the
308
+ * operator approves the server's verdict instead of a reconstruction of it.
309
+ */
310
+ async importProduce(input) {
311
+ return this.post("/produce/import", input);
312
+ }
313
+ /** Recipe ingredients still standing in for something vaguer. */
314
+ async listImpreciseIngredients(entityId, org) {
315
+ const params = new URLSearchParams({ entity: entityId });
316
+ if (org)
317
+ params.set("org", org);
318
+ return this.get(`/produce/ingredients?${params.toString()}`);
319
+ }
320
+ /** Point an imprecise ingredient at what it actually is. */
321
+ async refineIngredient(input) {
322
+ return this.patch("/produce/ingredients", input);
323
+ }
324
+ /**
325
+ * Grow the shared produce catalog. Admin only — every customer sees these
326
+ * concepts, so adding one is a platform decision, not a per-import shortcut.
327
+ */
328
+ async createCatalogConcepts(input) {
329
+ return this.post("/catalog/concepts", input);
330
+ }
331
+ /** Area sources, metadata only, newest first — the freshness evidence. */
332
+ async listBrainSources(areaId) {
333
+ return this.get(`/brain/sources?area=${encodeURIComponent(areaId)}`);
334
+ }
335
+ /** Entities with an active customer role and no investor/partner/producer role. */
336
+ async adminCustomersOnly() {
337
+ return this.get("/admin/customers-only");
338
+ }
339
+ /** Tips mailed to press@mail.dayofweek.com, read by the press-scan skill. */
340
+ async adminPressInbox(opts) {
341
+ const params = new URLSearchParams();
342
+ if (opts?.since)
343
+ params.set("since", opts.since);
344
+ if (opts?.limit)
345
+ params.set("limit", String(opts.limit));
346
+ const qs = params.toString();
347
+ return this.get(`/press-inbox${qs ? `?${qs}` : ""}`);
348
+ }
349
+ // ── Knowledge ─────────────────────────────────────────────────────────────
350
+ /**
351
+ * List the knowledge documents attached to an entity. `full` returns each
352
+ * document's whole content instead of an excerpt, which is what you want when
353
+ * mirroring an entity's sources into an external knowledge base.
354
+ */
355
+ async listKnowledge(opts) {
356
+ const params = new URLSearchParams({ entity: opts.entity });
357
+ if (opts.full)
358
+ params.set("full", "1");
359
+ if (opts.org)
360
+ params.set("org", opts.org);
361
+ return this.get(`/knowledge?${params.toString()}`);
362
+ }
363
+ async getKnowledge(documentId, org) {
364
+ const params = new URLSearchParams({ document: documentId });
365
+ if (org)
366
+ params.set("org", org);
367
+ return this.get(`/knowledge?${params.toString()}`);
368
+ }
369
+ /** Semantic search across knowledge documents. */
370
+ async searchKnowledge(opts) {
371
+ const params = new URLSearchParams({ q: opts.query });
372
+ if (opts.entity)
373
+ params.set("entity", opts.entity);
374
+ if (opts.types?.length)
375
+ params.set("types", opts.types.join(","));
376
+ if (opts.limit)
377
+ params.set("limit", String(opts.limit));
378
+ if (opts.allOrgs)
379
+ params.set("allOrgs", "1");
380
+ if (opts.org)
381
+ params.set("org", opts.org);
382
+ return this.get(`/knowledge?${params.toString()}`);
383
+ }
384
+ /**
385
+ * Add a markdown knowledge note.
386
+ *
387
+ * Without `direct` this submits a proposal for human review — the default,
388
+ * and the only option non-admin tokens have. With `direct: true` an admin
389
+ * token writes the document immediately, skipping review. Ask the operator
390
+ * before doing that; see references/admin.md in the served skill.
391
+ */
392
+ async addKnowledge(input) {
393
+ return this.post("/knowledge", input);
394
+ }
395
+ /**
396
+ * Attach a binary source (PDF, DOCX, XLSX, …) to an entity. Admin only.
397
+ *
398
+ * Three hops: ask for an upload URL, send the bytes straight to Convex
399
+ * storage, then register the resulting storageId. The bytes never pass
400
+ * through function arguments, so file size isn't bounded by an arg limit.
401
+ */
402
+ async attachKnowledgeFile(input) {
403
+ const { uploadUrl } = await this.post("/knowledge/file", {
404
+ entityId: input.entityId,
405
+ org: input.org,
406
+ });
407
+ const byteSize = input.data.byteLength;
408
+ const uploadRes = await fetch(uploadUrl, {
409
+ method: "POST",
410
+ headers: { "Content-Type": input.mimeType },
411
+ body: input.data,
412
+ });
413
+ if (!uploadRes.ok) {
414
+ throw new ApiError(uploadRes.status, `Upload to storage failed: ${uploadRes.status} ${uploadRes.statusText}`);
415
+ }
416
+ const uploaded = (await uploadRes.json());
417
+ if (!uploaded.storageId)
418
+ throw new Error("Storage upload returned no storageId");
419
+ return this.put("/knowledge/file", {
420
+ entityId: input.entityId,
421
+ org: input.org,
422
+ storageId: uploaded.storageId,
423
+ fileName: input.fileName,
424
+ mimeType: input.mimeType,
425
+ byteSize,
426
+ sourceType: input.sourceType,
427
+ sourceUrl: input.sourceUrl,
428
+ sourceDescription: input.sourceDescription,
429
+ });
430
+ }
431
+ // ── Datasets ──────────────────────────────────────────────────────────────
432
+ // The server owns the catalog: which datasets exist, what they are called,
433
+ // and who may read them. This client is deliberately name-blind — discovery
434
+ // happens at runtime so the open-source CLI reveals nothing about the
435
+ // platform's product surfaces.
436
+ async listDatasets(org) {
437
+ const qs = org ? `?org=${encodeURIComponent(org)}` : "";
438
+ return this.get(`/datasets${qs}`);
439
+ }
440
+ async readDataset(name, opts) {
441
+ const params = new URLSearchParams();
442
+ if (opts?.limit)
443
+ params.set("limit", String(opts.limit));
444
+ if (opts?.org)
445
+ params.set("org", opts.org);
446
+ const qs = params.toString();
447
+ return this.get(`/datasets/${encodeURIComponent(name)}${qs ? `?${qs}` : ""}`);
448
+ }
449
+ // ── Feedback backlog ──────────────────────────────────────────────────────
450
+ // Token scopes apply: read:feedback for the GETs, write:feedback to comment,
451
+ // admin:feedback for claim/status/priority. backOffice users have them all.
452
+ async listFeedback(opts) {
453
+ const params = new URLSearchParams();
454
+ if (opts?.status)
455
+ params.set("status", opts.status);
456
+ if (opts?.priority)
457
+ params.set("priority", opts.priority);
458
+ if (opts?.category)
459
+ params.set("category", opts.category);
460
+ if (opts?.limit)
461
+ params.set("limit", String(opts.limit));
462
+ const qs = params.toString();
463
+ return this.get(`/feedback${qs ? `?${qs}` : ""}`);
464
+ }
465
+ async getFeedbackItem(itemId) {
466
+ return this.get(`/feedback/${encodeURIComponent(itemId)}`);
467
+ }
468
+ /** Top-N from the heuristic prioritizer — "what should I work on next?". */
469
+ async feedbackRecommendations(limit) {
470
+ const qs = limit ? `?limit=${limit}` : "";
471
+ return this.get(`/feedback/ai-recommendations${qs}`);
472
+ }
473
+ async claimFeedbackItem(itemId) {
474
+ return this.post(`/feedback/${encodeURIComponent(itemId)}/claim`, {});
475
+ }
476
+ async commentOnFeedbackItem(itemId, body) {
477
+ return this.post(`/feedback/${encodeURIComponent(itemId)}/comments`, { body });
478
+ }
479
+ async updateFeedbackItem(itemId, updates) {
480
+ return this.patch(`/feedback/${encodeURIComponent(itemId)}`, updates);
481
+ }
482
+ // ── Customer emails (admin only) ──────────────────────────────────────────
483
+ // Sending is gated on explicit human approval by the skill, not by the API.
484
+ async listEmailThreads(opts) {
485
+ const params = new URLSearchParams();
486
+ if (opts?.status)
487
+ params.set("status", opts.status);
488
+ if (opts?.since)
489
+ params.set("since", opts.since);
490
+ if (opts?.customer)
491
+ params.set("customer", opts.customer);
492
+ if (opts?.limit)
493
+ params.set("limit", String(opts.limit));
494
+ if (opts?.org)
495
+ params.set("org", opts.org);
496
+ const qs = params.toString();
497
+ return this.get(`/emails${qs ? `?${qs}` : ""}`);
498
+ }
499
+ async getEmailThread(threadKey) {
500
+ return this.get(`/emails/${encodeURIComponent(threadKey)}`);
501
+ }
502
+ async replyToEmailThread(threadKey, input) {
503
+ return this.post(`/emails/${encodeURIComponent(threadKey)}/reply`, input);
504
+ }
505
+ async composeEmail(input) {
506
+ return this.post("/emails/compose", input);
507
+ }
282
508
  // ── Skill ─────────────────────────────────────────────────────────────────
283
509
  async getSkillBundle(name) {
284
510
  return this.get(`/skill${name ? `?name=${encodeURIComponent(name)}` : ""}`);
@@ -297,6 +523,9 @@ export class DayOfWeekClient {
297
523
  async post(path, body) {
298
524
  return this.request(path, { method: "POST", body: JSON.stringify(body) });
299
525
  }
526
+ async put(path, body) {
527
+ return this.request(path, { method: "PUT", body: JSON.stringify(body) });
528
+ }
300
529
  async patch(path, body) {
301
530
  return this.request(path, { method: "PATCH", body: JSON.stringify(body) });
302
531
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayofweek/dcli",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "CLI for the Day of Week AgTech platform — read data and submit proposals for review",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,13 +8,15 @@
8
8
  "dcli": "dist/bin/dcli.js"
9
9
  },
10
10
  "files": [
11
- "dist/"
11
+ "dist/",
12
+ "!dist/standalone"
12
13
  ],
13
14
  "engines": {
14
15
  "node": ">=18"
15
16
  },
16
17
  "scripts": {
17
- "build": "tsc",
18
+ "build": "tsc && node scripts/build-bundle.mjs",
19
+ "build:bundle": "node scripts/build-bundle.mjs",
18
20
  "dev": "tsx src/bin/dcli.ts",
19
21
  "test": "vitest run",
20
22
  "test:watch": "vitest",