@dayofweek/dcli 1.5.0 → 1.7.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
@@ -108,23 +108,27 @@ export declare class DayOfWeekClient {
108
108
  type?: string;
109
109
  parent?: string;
110
110
  limit?: number;
111
+ org?: string;
111
112
  }): Promise<any[]>;
112
- getEntity(entityId: string): Promise<any>;
113
+ getEntity(entityId: string, org?: string): Promise<any>;
113
114
  listProduce(opts?: {
114
115
  entity?: string;
115
116
  limit?: number;
117
+ org?: string;
116
118
  }): Promise<any[]>;
117
119
  listContacts(opts?: {
118
120
  entity?: string;
119
121
  limit?: number;
122
+ org?: string;
120
123
  }): Promise<any[]>;
121
- listEntityTypes(): Promise<any[]>;
124
+ listEntityTypes(org?: string): Promise<any[]>;
122
125
  searchCatalog(opts?: {
123
126
  search?: string;
124
127
  parent?: string;
125
128
  type?: string;
126
129
  includeCategories?: boolean;
127
130
  limit?: number;
131
+ org?: string;
128
132
  }): Promise<any[]>;
129
133
  listProposals(opts?: {
130
134
  status?: string;
@@ -159,6 +163,49 @@ export declare class DayOfWeekClient {
159
163
  truncated: boolean;
160
164
  total: number;
161
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>;
162
209
  /**
163
210
  * List the knowledge documents attached to an entity. `full` returns each
164
211
  * document's whole content instead of an excerpt, which is what you want when
@@ -217,6 +264,60 @@ export declare class DayOfWeekClient {
217
264
  sourceDescription?: string;
218
265
  org?: string;
219
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>;
220
321
  getSkillBundle(name?: string): Promise<{
221
322
  name: string;
222
323
  version: string;
@@ -232,6 +333,20 @@ export declare class DayOfWeekClient {
232
333
  version: string;
233
334
  hash: string;
234
335
  }>>;
336
+ listSharedSkills(): Promise<SharedSkillSummary[]>;
337
+ getSharedSkill(skillId: string): Promise<SharedSkillBundle>;
338
+ publishSharedSkill(input: {
339
+ areaId: string;
340
+ name: string;
341
+ version: string;
342
+ description?: string;
343
+ visibility: "area" | "company" | "global";
344
+ files: Array<{
345
+ path: string;
346
+ content: string;
347
+ }>;
348
+ }): Promise<SharedSkillSummary>;
349
+ archiveSharedSkill(skillId: string): Promise<SharedSkillSummary>;
235
350
  getSchema(): Promise<any>;
236
351
  private get;
237
352
  private post;
@@ -298,3 +413,26 @@ export type BrainResolvedResource = {
298
413
  resourceType: "source";
299
414
  source: BrainSource;
300
415
  };
416
+ export type SharedSkillSummary = {
417
+ id: string;
418
+ areaId: string;
419
+ areaName: string;
420
+ name: string;
421
+ version: string;
422
+ description?: string;
423
+ hash: string;
424
+ visibility: "area" | "company" | "global";
425
+ revision: number;
426
+ fileCount: number;
427
+ byteSize: number;
428
+ uri: string;
429
+ httpsUrl: string;
430
+ updatedAt: number;
431
+ };
432
+ export type SharedSkillBundle = Omit<SharedSkillSummary, "fileCount" | "byteSize"> & {
433
+ files: Array<{
434
+ path: string;
435
+ content: string;
436
+ sha256: string;
437
+ }>;
438
+ };
package/dist/client.js CHANGED
@@ -189,6 +189,8 @@ export class DayOfWeekClient {
189
189
  return this.get(`/brain/audit?${query}`);
190
190
  }
191
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.
192
194
  async listEntities(opts) {
193
195
  const params = new URLSearchParams();
194
196
  if (opts?.type)
@@ -197,11 +199,14 @@ export class DayOfWeekClient {
197
199
  params.set("parent", opts.parent);
198
200
  if (opts?.limit)
199
201
  params.set("limit", String(opts.limit));
202
+ if (opts?.org)
203
+ params.set("org", opts.org);
200
204
  const qs = params.toString();
201
205
  return this.get(`/entities${qs ? `?${qs}` : ""}`);
202
206
  }
203
- async getEntity(entityId) {
204
- return this.get(`/entities/${entityId}`);
207
+ async getEntity(entityId, org) {
208
+ const qs = org ? `?org=${encodeURIComponent(org)}` : "";
209
+ return this.get(`/entities/${entityId}${qs}`);
205
210
  }
206
211
  async listProduce(opts) {
207
212
  const params = new URLSearchParams();
@@ -209,6 +214,8 @@ export class DayOfWeekClient {
209
214
  params.set("entity", opts.entity);
210
215
  if (opts?.limit)
211
216
  params.set("limit", String(opts.limit));
217
+ if (opts?.org)
218
+ params.set("org", opts.org);
212
219
  const qs = params.toString();
213
220
  return this.get(`/produce${qs ? `?${qs}` : ""}`);
214
221
  }
@@ -218,11 +225,14 @@ export class DayOfWeekClient {
218
225
  params.set("entity", opts.entity);
219
226
  if (opts?.limit)
220
227
  params.set("limit", String(opts.limit));
228
+ if (opts?.org)
229
+ params.set("org", opts.org);
221
230
  const qs = params.toString();
222
231
  return this.get(`/contacts${qs ? `?${qs}` : ""}`);
223
232
  }
224
- async listEntityTypes() {
225
- return this.get("/entity-types");
233
+ async listEntityTypes(org) {
234
+ const qs = org ? `?org=${encodeURIComponent(org)}` : "";
235
+ return this.get(`/entity-types${qs}`);
226
236
  }
227
237
  async searchCatalog(opts) {
228
238
  const params = new URLSearchParams();
@@ -236,6 +246,8 @@ export class DayOfWeekClient {
236
246
  params.set("includeCategories", "true");
237
247
  if (opts?.limit)
238
248
  params.set("limit", String(opts.limit));
249
+ if (opts?.org)
250
+ params.set("org", opts.org);
239
251
  const qs = params.toString();
240
252
  return this.get(`/catalog${qs ? `?${qs}` : ""}`);
241
253
  }
@@ -288,6 +300,52 @@ export class DayOfWeekClient {
288
300
  const qs = params.toString();
289
301
  return this.get(`/admin/proposals${qs ? `?${qs}` : ""}`);
290
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
+ }
291
349
  // ── Knowledge ─────────────────────────────────────────────────────────────
292
350
  /**
293
351
  * List the knowledge documents attached to an entity. `full` returns each
@@ -370,6 +428,83 @@ export class DayOfWeekClient {
370
428
  sourceDescription: input.sourceDescription,
371
429
  });
372
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
+ }
373
508
  // ── Skill ─────────────────────────────────────────────────────────────────
374
509
  async getSkillBundle(name) {
375
510
  return this.get(`/skill${name ? `?name=${encodeURIComponent(name)}` : ""}`);
@@ -377,6 +512,23 @@ export class DayOfWeekClient {
377
512
  async listSkillBundles() {
378
513
  return this.get("/skill?list=1");
379
514
  }
515
+ // ── Shared skills ─────────────────────────────────────────────────────────
516
+ //
517
+ // Skills other users published into shared knowledge areas. Discovery is
518
+ // server-owned, like the named bundles above: the CLI ships no skill names,
519
+ // and the server decides which skills this device's user may see.
520
+ async listSharedSkills() {
521
+ return this.get("/brain/skills");
522
+ }
523
+ async getSharedSkill(skillId) {
524
+ return this.get(`/brain/skills/${encodeURIComponent(skillId)}`);
525
+ }
526
+ async publishSharedSkill(input) {
527
+ return this.post("/brain/skills", input);
528
+ }
529
+ async archiveSharedSkill(skillId) {
530
+ return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
531
+ }
380
532
  // ── Schema ────────────────────────────────────────────────────────────────
381
533
  async getSchema() {
382
534
  return this.get("/schema");
package/dist/skills.d.ts CHANGED
@@ -8,8 +8,31 @@ export type SkillBundle = {
8
8
  sha256?: string;
9
9
  }>;
10
10
  };
11
+ /**
12
+ * Where an installed skill came from — recorded in `.dayofweek-skill.json` so
13
+ * `skill update` and `skill status --check` can go back to the same origin.
14
+ * "platform" = a named bundle from the built-in endpoint; "shared" = a skill
15
+ * another user published into a shared knowledge area.
16
+ */
17
+ export type SkillOrigin = {
18
+ source: "platform";
19
+ } | {
20
+ source: "shared";
21
+ skillId: string;
22
+ areaId: string;
23
+ uri: string;
24
+ };
25
+ export type InstalledSkillMetadata = {
26
+ name: string;
27
+ version: string;
28
+ hash: string;
29
+ files: Record<string, string>;
30
+ origin?: SkillOrigin;
31
+ };
32
+ /** Parse `<dir>/.dayofweek-skill.json`; null when absent or malformed. */
33
+ export declare function readInstalledSkill(targetDir: string): InstalledSkillMetadata | null;
11
34
  export declare function validateSkillBundle(bundle: SkillBundle): SkillBundle;
12
- export declare function writeSkillBundle(input: SkillBundle, targetDir: string): {
35
+ export declare function writeSkillBundle(input: SkillBundle, targetDir: string, origin?: SkillOrigin): {
13
36
  written: number;
14
37
  unchanged: number;
15
38
  conflicts: string[];
package/dist/skills.js CHANGED
@@ -1,6 +1,21 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
+ /** Parse `<dir>/.dayofweek-skill.json`; null when absent or malformed. */
5
+ export function readInstalledSkill(targetDir) {
6
+ const metadataPath = join(targetDir, ".dayofweek-skill.json");
7
+ if (!existsSync(metadataPath))
8
+ return null;
9
+ try {
10
+ const parsed = JSON.parse(readFileSync(metadataPath, "utf8"));
11
+ if (typeof parsed.name !== "string" || typeof parsed.version !== "string")
12
+ return null;
13
+ return parsed;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
4
19
  function sha256(value) {
5
20
  return createHash("sha256").update(value).digest("hex");
6
21
  }
@@ -33,7 +48,7 @@ export function validateSkillBundle(bundle) {
33
48
  throw new Error("Skill bundle manifest checksum mismatch");
34
49
  return { ...bundle, hash: actualBundleHash, files };
35
50
  }
36
- export function writeSkillBundle(input, targetDir) {
51
+ export function writeSkillBundle(input, targetDir, origin) {
37
52
  const bundle = validateSkillBundle(input);
38
53
  let filesWritten = 0;
39
54
  let unchanged = 0;
@@ -72,6 +87,6 @@ export function writeSkillBundle(input, targetDir) {
72
87
  filesWritten++;
73
88
  }
74
89
  mkdirSync(targetDir, { recursive: true });
75
- writeFileSync(metadataPath, JSON.stringify({ name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes }, null, 2), "utf8");
90
+ writeFileSync(metadataPath, JSON.stringify({ name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes, ...(origin ? { origin } : {}) }, null, 2), "utf8");
76
91
  return { written: filesWritten, unchanged, conflicts };
77
92
  }
package/dist/uri.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type BrainResource = {
2
2
  version: 1;
3
3
  areaId: string;
4
- resourceType: "area" | "note" | "source";
4
+ resourceType: "area" | "note" | "source" | "skill";
5
5
  resourceId?: string;
6
6
  };
7
7
  export declare function parseBrainResource(input: string): BrainResource;
package/dist/uri.js CHANGED
@@ -21,7 +21,7 @@ function parseSegments(pathname) {
21
21
  throw new Error("Unsupported brain URI form");
22
22
  const areaId = assertId(segments[0], "area ID");
23
23
  const resourceType = segments[1];
24
- if (resourceType !== "note" && resourceType !== "source") {
24
+ if (resourceType !== "note" && resourceType !== "source" && resourceType !== "skill") {
25
25
  throw new Error("Unsupported brain resource type");
26
26
  }
27
27
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dayofweek/dcli",
3
- "version": "1.5.0",
3
+ "version": "1.7.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",