@dayofweek/dcli 1.6.0 → 1.8.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
@@ -72,6 +72,34 @@ Updates overwrite only files whose installed hash still matches the managed mani
72
72
 
73
73
  The legacy entity/proposal Agent Skill remains available through `dcli skill install` without a bundle name.
74
74
 
75
+ ### Shared skills
76
+
77
+ Beyond the named bundles above, users can publish their own skills into a shared knowledge area. Anyone who can read the area — or, with `--visibility company`, anyone in the publishing area's organization — can then discover, install, and update the skill. Service administrators can additionally publish with `--visibility global`, making a skill a shared starter for every user of the service; the desktop app syncs global skills into managed wikis automatically.
78
+
79
+ ```bash
80
+ # Publish a local skill directory (name/version come from SKILL.md frontmatter)
81
+ dcli skill publish --area <areaId> --dir .agents/skills/meeting-notes --visibility company
82
+
83
+ # Discover: --shared adds skills shared with you to the bundle listing
84
+ dcli skill list --shared --json
85
+
86
+ # Install by canonical URI, or by name + area
87
+ dcli skill install 'dayofweek://brain/<areaId>/skill/<skillId>'
88
+ dcli skill install meeting-notes --area <areaId>
89
+
90
+ # Update goes back to wherever the install came from
91
+ dcli skill update meeting-notes --area <areaId>
92
+ dcli skill update --dir .agents/skills/meeting-notes
93
+
94
+ # Check for newer versions without writing anything
95
+ dcli skill status meeting-notes --dir .agents/skills/meeting-notes --check
96
+
97
+ # Retire a shared skill you own
98
+ dcli skill archive 'dayofweek://brain/<areaId>/skill/<skillId>'
99
+ ```
100
+
101
+ Shared skills go through the same integrity pipeline as named bundles: per-file SHA-256 plus a manifest hash computed server-side and re-verified locally before anything touches disk, the same path-safety rules, and the same conflict handling — locally edited files are never overwritten. The install origin is recorded in `.dayofweek-skill.json`, so `skill update` and `skill status --check` know whether to ask the bundle endpoint or the shared-skill endpoint. Publishing requires write access to the area; `--visibility company` and `skill archive` require ownership.
102
+
75
103
  ## URI contract
76
104
 
77
105
  `dcli` accepts only exact canonical resource forms:
package/dist/bin/dcli.js CHANGED
@@ -4,13 +4,13 @@ import { ApiError, DayOfWeekClient, toArrayBuffer } from "../client.js";
4
4
  import { getToken, getApiUrl, saveConfig, loadConfig, saveCredential, deleteCredential } from "../config.js";
5
5
  import { browserLogin } from "../auth/login.js";
6
6
  import { parseBrainResource } from "../uri.js";
7
- import { accessSync, constants, mkdirSync, readFileSync, existsSync, statSync, writeFileSync } from "node:fs";
7
+ import { accessSync, constants, mkdirSync, readdirSync, readFileSync, existsSync, statSync, writeFileSync } from "node:fs";
8
8
  import { join, basename, resolve, relative, sep } from "node:path";
9
9
  import { homedir } from "node:os";
10
10
  import { createInterface } from "node:readline/promises";
11
11
  import pkg from "../../package.json" with { type: "json" };
12
12
  import { createHash } from "node:crypto";
13
- import { validateSkillBundle, writeSkillBundle } from "../skills.js";
13
+ import { readInstalledSkill, validateSkillBundle, writeSkillBundle } from "../skills.js";
14
14
  const program = new Command()
15
15
  .name("dcli")
16
16
  .description("CLI for the Day of Week AgTech platform")
@@ -298,6 +298,36 @@ brainSource
298
298
  overwrite: opts.overwrite,
299
299
  }));
300
300
  });
301
+ const brainActors = brain
302
+ .command("actors")
303
+ .description("Work with an area's actors (people and organizations)");
304
+ brainActors
305
+ .command("list")
306
+ .description("List an area's actors (active, sorted by name)")
307
+ .requiredOption("--area <areaId>", "Area id (from `brain list`)")
308
+ .action(async (opts) => {
309
+ output(await getClient().listBrainActors(opts.area));
310
+ });
311
+ brainActors
312
+ .command("add")
313
+ .description("Add an actor to an area (idempotent on name)")
314
+ .requiredOption("--area <areaId>", "Area id (from `brain list`)")
315
+ .requiredOption("--name <name>", "Actor name (person or organization)")
316
+ .option("--kind <kind>", "person | organization", "organization")
317
+ .option("--role <text>", "Role or relationship in the project")
318
+ .option("--description <text>", "Longer free-text description")
319
+ .action(async (opts) => {
320
+ if (opts.kind !== "person" && opts.kind !== "organization") {
321
+ throw new Error(`--kind must be "person" or "organization", got "${opts.kind}"`);
322
+ }
323
+ output(await getClient().createBrainActor({
324
+ areaId: opts.area,
325
+ name: opts.name,
326
+ kind: opts.kind,
327
+ role: opts.role,
328
+ description: opts.description,
329
+ }));
330
+ });
301
331
  auth
302
332
  .command("devices")
303
333
  .description("List your agent tokens")
@@ -768,53 +798,207 @@ function parseTarget(value) {
768
798
  }
769
799
  return v;
770
800
  }
801
+ function sharedToBundle(shared) {
802
+ return {
803
+ bundle: { name: shared.name, version: shared.version, hash: shared.hash, files: shared.files },
804
+ origin: { source: "shared", skillId: shared.id, areaId: shared.areaId, uri: shared.uri },
805
+ };
806
+ }
807
+ async function fetchSkillByOrigin(client, nameOrUri, areaId) {
808
+ if (nameOrUri?.includes("://")) {
809
+ const resource = parseBrainResource(nameOrUri);
810
+ if (resource.resourceType !== "skill" || !resource.resourceId)
811
+ throw new Error("A shared skill URI is required");
812
+ const shared = await client.getSharedSkill(resource.resourceId);
813
+ if (shared.areaId !== resource.areaId)
814
+ throw new Error("Server returned a mismatched area");
815
+ return sharedToBundle(shared);
816
+ }
817
+ if (areaId) {
818
+ if (!nameOrUri)
819
+ throw new Error("A skill name is required together with --area");
820
+ const match = (await client.listSharedSkills()).find((candidate) => candidate.areaId === areaId && candidate.name === nameOrUri);
821
+ if (!match)
822
+ throw new Error("Shared skill not found in that area");
823
+ return sharedToBundle(await client.getSharedSkill(match.id));
824
+ }
825
+ return { bundle: await client.getSkillBundle(nameOrUri), origin: { source: "platform" } };
826
+ }
827
+ async function installOrUpdateSkill(action, name, opts) {
828
+ const client = getClient();
829
+ let resolved;
830
+ if (action === "update" && !name && !opts.area && opts.dir) {
831
+ // Bare `skill update --dir …`: go back to wherever this install came from.
832
+ const installed = readInstalledSkill(opts.dir);
833
+ if (installed) {
834
+ resolved = installed.origin?.source === "shared"
835
+ ? sharedToBundle(await client.getSharedSkill(installed.origin.skillId))
836
+ : { bundle: await client.getSkillBundle(installed.name), origin: { source: "platform" } };
837
+ }
838
+ }
839
+ resolved = resolved ?? await fetchSkillByOrigin(client, name, opts.area);
840
+ const bundle = validateSkillBundle(resolved.bundle);
841
+ const dirs = resolveTargetDirs(parseTarget(opts.target), bundle.name, opts.dir);
842
+ const installations = [];
843
+ for (const dir of dirs) {
844
+ const result = writeSkillBundle(bundle, dir, resolved.origin);
845
+ installations.push({ directory: dir, ...result });
846
+ }
847
+ output({
848
+ action,
849
+ bundle: bundle.name,
850
+ version: bundle.version,
851
+ hash: bundle.hash,
852
+ origin: resolved.origin.source,
853
+ ...(resolved.origin.source === "shared" ? { uri: resolved.origin.uri } : {}),
854
+ installations,
855
+ });
856
+ }
857
+ /** Read a skill directory into bundle files: relative paths, utf-8 content. */
858
+ function collectSkillFiles(dir) {
859
+ const root = resolve(dir);
860
+ if (!existsSync(root) || !statSync(root).isDirectory())
861
+ throw new Error("Skill directory not found");
862
+ const files = [];
863
+ const walk = (current, prefix) => {
864
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
865
+ // Hidden entries (including the .dayofweek-skill.json install manifest)
866
+ // and update-conflict artifacts never belong in a published bundle.
867
+ if (entry.name.startsWith(".") || /\.new(\.\d+)?$/.test(entry.name))
868
+ continue;
869
+ if (entry.isSymbolicLink())
870
+ throw new Error("Symlinks are not allowed in a skill directory");
871
+ const full = join(current, entry.name);
872
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
873
+ if (entry.isDirectory())
874
+ walk(full, relativePath);
875
+ else if (entry.isFile())
876
+ files.push({ path: relativePath, content: readFileSync(full, "utf8") });
877
+ }
878
+ };
879
+ walk(root, "");
880
+ return files.sort((left, right) => left.path.localeCompare(right.path));
881
+ }
771
882
  skill
772
883
  .command("list")
773
- .description("List authenticated named skill bundles")
774
- .action(async () => output(await getClient().listSkillBundles()));
884
+ .description("List installable named skill bundles; --shared adds skills shared with you")
885
+ .option("--shared", "Include skills other users shared with you")
886
+ .action(async (opts) => {
887
+ const client = getClient();
888
+ // The bare listing stays exactly the named bundles: managed installers
889
+ // iterate it and fetch every entry by plain name, so shared skills (which
890
+ // resolve by URI) only appear when explicitly asked for.
891
+ const listings = (await client.listSkillBundles()).map((bundle) => ({
892
+ ...bundle,
893
+ source: "platform",
894
+ }));
895
+ if (opts.shared) {
896
+ try {
897
+ for (const shared of await client.listSharedSkills()) {
898
+ listings.push({
899
+ name: shared.name,
900
+ version: shared.version,
901
+ hash: shared.hash,
902
+ source: "shared",
903
+ areaId: shared.areaId,
904
+ areaName: shared.areaName,
905
+ visibility: shared.visibility,
906
+ description: shared.description,
907
+ uri: shared.uri,
908
+ updatedAt: shared.updatedAt,
909
+ });
910
+ }
911
+ }
912
+ catch (error) {
913
+ // Older servers or tokens without knowledge scopes have no shared-skill
914
+ // surface — the named bundles are still worth listing.
915
+ console.error(`Shared skills unavailable: ${error instanceof Error ? error.message : String(error)}`);
916
+ }
917
+ }
918
+ output(listings.sort((left, right) => left.name.localeCompare(right.name)));
919
+ });
775
920
  skill
776
- .command("bundle <name>")
777
- .description("Fetch and verify a named skill bundle for a managed installer")
778
- .action(async (name) => output(validateSkillBundle(await getClient().getSkillBundle(name))));
921
+ .command("bundle <nameOrUri>")
922
+ .description("Fetch and verify a skill bundle (by name, or shared-skill URI) for a managed installer")
923
+ .action(async (nameOrUri) => {
924
+ if (nameOrUri.includes("://")) {
925
+ const resource = parseBrainResource(nameOrUri);
926
+ if (resource.resourceType !== "skill" || !resource.resourceId)
927
+ throw new Error("A shared skill URI is required");
928
+ const shared = await getClient().getSharedSkill(resource.resourceId);
929
+ if (shared.areaId !== resource.areaId)
930
+ throw new Error("Server returned a mismatched area");
931
+ // Emit the plain bundle shape managed installers expect.
932
+ output(validateSkillBundle({ name: shared.name, version: shared.version, hash: shared.hash, files: shared.files }));
933
+ return;
934
+ }
935
+ output(validateSkillBundle(await getClient().getSkillBundle(nameOrUri)));
936
+ });
779
937
  skill
780
938
  .command("install [name]")
781
- .description("Install the agent skill (requires valid auth)")
939
+ .description("Install a skill by bundle name, shared-skill URI, or name + --area")
782
940
  .option("--dir <path>", "Custom install directory (overrides --target)")
783
941
  .option("--target <target>", "Install target: agents, claude, or all (default: all)")
784
- .action(async (name, opts) => {
785
- const client = getClient();
786
- const bundle = await client.getSkillBundle(name);
787
- const target = parseTarget(opts.target);
788
- const dirs = resolveTargetDirs(target, bundle.name, opts.dir);
789
- const installations = [];
790
- for (const dir of dirs) {
791
- const result = writeSkillBundle(bundle, dir);
792
- installations.push({ directory: dir, ...result });
793
- }
794
- output({ action: "install", bundle: bundle.name, version: bundle.version, hash: bundle.hash, installations });
795
- });
942
+ .option("--area <areaId>", "Install a shared skill from this area")
943
+ .action(async (name, opts) => installOrUpdateSkill("install", name, opts));
796
944
  skill
797
945
  .command("update [name]")
798
- .description("Update the skill to the latest version")
946
+ .description("Update a skill to the latest version from its origin")
799
947
  .option("--dir <path>", "Custom install directory (overrides --target)")
800
948
  .option("--target <target>", "Install target: agents, claude, or all (default: all)")
801
- .action(async (name, opts) => {
802
- const client = getClient();
803
- const bundle = await client.getSkillBundle(name);
804
- const target = parseTarget(opts.target);
805
- const dirs = resolveTargetDirs(target, bundle.name, opts.dir);
806
- const installations = [];
807
- for (const dir of dirs) {
808
- const result = writeSkillBundle(bundle, dir);
809
- installations.push({ directory: dir, ...result });
949
+ .option("--area <areaId>", "Update a shared skill from this area")
950
+ .action(async (name, opts) => installOrUpdateSkill("update", name, opts));
951
+ skill
952
+ .command("publish")
953
+ .description("Share a skill directory to a knowledge area so teammates can install it")
954
+ .requiredOption("--area <areaId>", "Owning area (see: dcli brain list)")
955
+ .requiredOption("--dir <path>", "Skill directory containing SKILL.md")
956
+ .option("--name <name>", "Skill name (default: SKILL.md frontmatter, else the directory name)")
957
+ .option("--skill-version <version>", "Version string (default: SKILL.md frontmatter version)")
958
+ .option("--visibility <visibility>", "Who can discover it: area (members only), company, or global (admins; every user)")
959
+ .option("--description <text>", "Short description shown in listings")
960
+ .action(async (opts) => {
961
+ const visibility = (opts.visibility ?? "area").toLowerCase();
962
+ if (visibility !== "area" && visibility !== "company" && visibility !== "global") {
963
+ throw new Error("Invalid --visibility: use area, company, or global");
810
964
  }
811
- output({ action: "update", bundle: bundle.name, version: bundle.version, hash: bundle.hash, installations });
965
+ const files = collectSkillFiles(opts.dir);
966
+ const skillMd = files.find((file) => file.path === "SKILL.md");
967
+ if (!skillMd)
968
+ throw new Error("The skill directory must contain SKILL.md");
969
+ const name = opts.name
970
+ ?? skillMd.content.match(/^name:\s*"?([a-z0-9][a-z0-9-]*)"?\s*$/m)?.[1]
971
+ ?? basename(resolve(opts.dir));
972
+ const version = opts.skillVersion ?? skillMd.content.match(/version:\s*"([^"]+)"/)?.[1] ?? "1.0.0";
973
+ validateSkillBundle({ name, version, files });
974
+ const result = await getClient().publishSharedSkill({
975
+ areaId: opts.area,
976
+ name,
977
+ version,
978
+ description: opts.description,
979
+ visibility,
980
+ files,
981
+ });
982
+ output({ action: "publish", ...result });
983
+ });
984
+ skill
985
+ .command("archive <uri>")
986
+ .description("Archive a shared skill you own (removes it from listing and install)")
987
+ .action(async (uri) => {
988
+ const resource = parseBrainResource(uri);
989
+ if (resource.resourceType !== "skill" || !resource.resourceId)
990
+ throw new Error("A shared skill URI is required");
991
+ const result = await getClient().archiveSharedSkill(resource.resourceId);
992
+ if (result.areaId !== resource.areaId)
993
+ throw new Error("Server returned a mismatched area");
994
+ output({ action: "archive", ...result });
812
995
  });
813
996
  skill
814
997
  .command("status [name]")
815
- .description("Check if the skill is installed")
998
+ .description("Check if the skill is installed; --check compares against its origin")
816
999
  .option("--dir <path>", "Custom install directory (overrides --target)")
817
1000
  .option("--target <target>", "Check target: agents, claude, or all (default: all)")
1001
+ .option("--check", "Also ask the server whether a newer version exists")
818
1002
  .action(async (name, opts) => {
819
1003
  const bundleName = name ?? "dayofweek-platform";
820
1004
  const target = parseTarget(opts.target);
@@ -823,6 +1007,7 @@ skill
823
1007
  : target === "all"
824
1008
  ? [join(homedir(), ".agents", "skills", bundleName), join(homedir(), ".claude", "skills", bundleName)]
825
1009
  : resolveTargetDirs(target, bundleName);
1010
+ let platformBundles;
826
1011
  const installations = [];
827
1012
  for (const dir of dirs) {
828
1013
  const skillPath = join(dir, "SKILL.md");
@@ -830,9 +1015,40 @@ skill
830
1015
  installations.push({ installed: false, directory: dir, name: bundleName });
831
1016
  continue;
832
1017
  }
1018
+ const metadata = readInstalledSkill(dir);
833
1019
  const content = readFileSync(skillPath, "utf-8");
834
1020
  const versionMatch = content.match(/version:\s*"([^"]+)"/);
835
- installations.push({ installed: true, directory: dir, name: bundleName, version: versionMatch?.[1] ?? "unknown", sha256: sha256(content) });
1021
+ const entry = {
1022
+ installed: true,
1023
+ directory: dir,
1024
+ name: metadata?.name ?? bundleName,
1025
+ version: metadata?.version ?? versionMatch?.[1] ?? "unknown",
1026
+ sha256: sha256(content),
1027
+ origin: metadata?.origin?.source ?? "platform",
1028
+ };
1029
+ if (opts.check) {
1030
+ try {
1031
+ if (metadata?.origin?.source === "shared") {
1032
+ const latest = await getClient().getSharedSkill(metadata.origin.skillId);
1033
+ entry.latestVersion = latest.version;
1034
+ entry.upToDate = metadata.hash === latest.hash;
1035
+ }
1036
+ else {
1037
+ platformBundles ??= await getClient().listSkillBundles();
1038
+ const latest = platformBundles.find((candidate) => candidate.name === entry.name);
1039
+ if (latest) {
1040
+ entry.latestVersion = latest.version;
1041
+ // Pre-origin installs have no recorded manifest hash; fall back
1042
+ // to comparing the declared versions.
1043
+ entry.upToDate = metadata ? metadata.hash === latest.hash : latest.version === entry.version;
1044
+ }
1045
+ }
1046
+ }
1047
+ catch (error) {
1048
+ entry.checkError = error instanceof Error ? error.message : String(error);
1049
+ }
1050
+ }
1051
+ installations.push(entry);
836
1052
  }
837
1053
  const installed = installations.some((entry) => entry.installed);
838
1054
  output({ installed, bundle: bundleName, installations });
@@ -4472,6 +4472,18 @@ var DayOfWeekClient = class {
4472
4472
  async listBrainSources(areaId) {
4473
4473
  return this.get(`/brain/sources?area=${encodeURIComponent(areaId)}`);
4474
4474
  }
4475
+ /** An area's actors (people and organizations, the Actors tab). Active only. */
4476
+ async listBrainActors(areaId) {
4477
+ return this.get(`/brain/actors?area=${encodeURIComponent(areaId)}`);
4478
+ }
4479
+ /**
4480
+ * Create an actor in an area. Idempotent on (area, name): an existing active
4481
+ * actor comes back with `created: false` instead of a duplicate, so imports
4482
+ * can re-run safely.
4483
+ */
4484
+ async createBrainActor(input) {
4485
+ return this.post("/brain/actors", input);
4486
+ }
4475
4487
  /** Entities with an active customer role and no investor/partner/producer role. */
4476
4488
  async adminCustomersOnly() {
4477
4489
  return this.get("/admin/customers-only");
@@ -4633,6 +4645,23 @@ var DayOfWeekClient = class {
4633
4645
  async listSkillBundles() {
4634
4646
  return this.get("/skill?list=1");
4635
4647
  }
4648
+ // ── Shared skills ─────────────────────────────────────────────────────────
4649
+ //
4650
+ // Skills other users published into shared knowledge areas. Discovery is
4651
+ // server-owned, like the named bundles above: the CLI ships no skill names,
4652
+ // and the server decides which skills this device's user may see.
4653
+ async listSharedSkills() {
4654
+ return this.get("/brain/skills");
4655
+ }
4656
+ async getSharedSkill(skillId) {
4657
+ return this.get(`/brain/skills/${encodeURIComponent(skillId)}`);
4658
+ }
4659
+ async publishSharedSkill(input) {
4660
+ return this.post("/brain/skills", input);
4661
+ }
4662
+ async archiveSharedSkill(skillId) {
4663
+ return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
4664
+ }
4636
4665
  // ── Schema ────────────────────────────────────────────────────────────────
4637
4666
  async getSchema() {
4638
4667
  return this.get("/schema");
@@ -5037,7 +5066,7 @@ function parseSegments(pathname) {
5037
5066
  if (segments.length !== 3) throw new Error("Unsupported brain URI form");
5038
5067
  const areaId = assertId(segments[0], "area ID");
5039
5068
  const resourceType = segments[1];
5040
- if (resourceType !== "note" && resourceType !== "source") {
5069
+ if (resourceType !== "note" && resourceType !== "source" && resourceType !== "skill") {
5041
5070
  throw new Error("Unsupported brain resource type");
5042
5071
  }
5043
5072
  return {
@@ -5081,7 +5110,7 @@ var import_promises4 = require("node:readline/promises");
5081
5110
  // package.json
5082
5111
  var package_default = {
5083
5112
  name: "@dayofweek/dcli",
5084
- version: "1.6.0",
5113
+ version: "1.8.0",
5085
5114
  description: "CLI for the Day of Week AgTech platform \u2014 read data and submit proposals for review",
5086
5115
  license: "MIT",
5087
5116
  type: "module",
@@ -5141,6 +5170,17 @@ var import_node_crypto4 = require("node:crypto");
5141
5170
  var import_node_crypto3 = require("node:crypto");
5142
5171
  var import_node_fs7 = require("node:fs");
5143
5172
  var import_node_path5 = require("node:path");
5173
+ function readInstalledSkill(targetDir) {
5174
+ const metadataPath = (0, import_node_path5.join)(targetDir, ".dayofweek-skill.json");
5175
+ if (!(0, import_node_fs7.existsSync)(metadataPath)) return null;
5176
+ try {
5177
+ const parsed = JSON.parse((0, import_node_fs7.readFileSync)(metadataPath, "utf8"));
5178
+ if (typeof parsed.name !== "string" || typeof parsed.version !== "string") return null;
5179
+ return parsed;
5180
+ } catch {
5181
+ return null;
5182
+ }
5183
+ }
5144
5184
  function sha256(value) {
5145
5185
  return (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
5146
5186
  }
@@ -5164,7 +5204,7 @@ function validateSkillBundle(bundle) {
5164
5204
  if (bundle.hash && bundle.hash !== actualBundleHash) throw new Error("Skill bundle manifest checksum mismatch");
5165
5205
  return { ...bundle, hash: actualBundleHash, files };
5166
5206
  }
5167
- function writeSkillBundle(input, targetDir) {
5207
+ function writeSkillBundle(input, targetDir, origin) {
5168
5208
  const bundle = validateSkillBundle(input);
5169
5209
  let filesWritten = 0;
5170
5210
  let unchanged = 0;
@@ -5200,7 +5240,15 @@ function writeSkillBundle(input, targetDir) {
5200
5240
  filesWritten++;
5201
5241
  }
5202
5242
  (0, import_node_fs7.mkdirSync)(targetDir, { recursive: true });
5203
- (0, import_node_fs7.writeFileSync)(metadataPath, JSON.stringify({ name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes }, null, 2), "utf8");
5243
+ (0, import_node_fs7.writeFileSync)(
5244
+ metadataPath,
5245
+ JSON.stringify(
5246
+ { name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes, ...origin ? { origin } : {} },
5247
+ null,
5248
+ 2
5249
+ ),
5250
+ "utf8"
5251
+ );
5204
5252
  return { written: filesWritten, unchanged, conflicts };
5205
5253
  }
5206
5254
 
@@ -5377,6 +5425,22 @@ brainSource.command("download <uri>").description("Download exact original bytes
5377
5425
  overwrite: opts.overwrite
5378
5426
  }));
5379
5427
  });
5428
+ var brainActors = brain.command("actors").description("Work with an area's actors (people and organizations)");
5429
+ brainActors.command("list").description("List an area's actors (active, sorted by name)").requiredOption("--area <areaId>", "Area id (from `brain list`)").action(async (opts) => {
5430
+ output(await getClient().listBrainActors(opts.area));
5431
+ });
5432
+ brainActors.command("add").description("Add an actor to an area (idempotent on name)").requiredOption("--area <areaId>", "Area id (from `brain list`)").requiredOption("--name <name>", "Actor name (person or organization)").option("--kind <kind>", "person | organization", "organization").option("--role <text>", "Role or relationship in the project").option("--description <text>", "Longer free-text description").action(async (opts) => {
5433
+ if (opts.kind !== "person" && opts.kind !== "organization") {
5434
+ throw new Error(`--kind must be "person" or "organization", got "${opts.kind}"`);
5435
+ }
5436
+ output(await getClient().createBrainActor({
5437
+ areaId: opts.area,
5438
+ name: opts.name,
5439
+ kind: opts.kind,
5440
+ role: opts.role,
5441
+ description: opts.description
5442
+ }));
5443
+ });
5380
5444
  auth.command("devices").description("List your agent tokens").action(async () => {
5381
5445
  const client = getClient();
5382
5446
  const devices = await client.listDevices();
@@ -5699,36 +5763,148 @@ function parseTarget(value) {
5699
5763
  }
5700
5764
  return v;
5701
5765
  }
5702
- skill.command("list").description("List authenticated named skill bundles").action(async () => output(await getClient().listSkillBundles()));
5703
- skill.command("bundle <name>").description("Fetch and verify a named skill bundle for a managed installer").action(async (name) => output(validateSkillBundle(await getClient().getSkillBundle(name))));
5704
- skill.command("install [name]").description("Install the agent skill (requires valid auth)").option("--dir <path>", "Custom install directory (overrides --target)").option("--target <target>", "Install target: agents, claude, or all (default: all)").action(async (name, opts) => {
5766
+ function sharedToBundle(shared) {
5767
+ return {
5768
+ bundle: { name: shared.name, version: shared.version, hash: shared.hash, files: shared.files },
5769
+ origin: { source: "shared", skillId: shared.id, areaId: shared.areaId, uri: shared.uri }
5770
+ };
5771
+ }
5772
+ async function fetchSkillByOrigin(client, nameOrUri, areaId) {
5773
+ if (nameOrUri?.includes("://")) {
5774
+ const resource = parseBrainResource(nameOrUri);
5775
+ if (resource.resourceType !== "skill" || !resource.resourceId) throw new Error("A shared skill URI is required");
5776
+ const shared = await client.getSharedSkill(resource.resourceId);
5777
+ if (shared.areaId !== resource.areaId) throw new Error("Server returned a mismatched area");
5778
+ return sharedToBundle(shared);
5779
+ }
5780
+ if (areaId) {
5781
+ if (!nameOrUri) throw new Error("A skill name is required together with --area");
5782
+ const match = (await client.listSharedSkills()).find(
5783
+ (candidate) => candidate.areaId === areaId && candidate.name === nameOrUri
5784
+ );
5785
+ if (!match) throw new Error("Shared skill not found in that area");
5786
+ return sharedToBundle(await client.getSharedSkill(match.id));
5787
+ }
5788
+ return { bundle: await client.getSkillBundle(nameOrUri), origin: { source: "platform" } };
5789
+ }
5790
+ async function installOrUpdateSkill(action, name, opts) {
5705
5791
  const client = getClient();
5706
- const bundle = await client.getSkillBundle(name);
5707
- const target = parseTarget(opts.target);
5708
- const dirs = resolveTargetDirs(target, bundle.name, opts.dir);
5792
+ let resolved;
5793
+ if (action === "update" && !name && !opts.area && opts.dir) {
5794
+ const installed = readInstalledSkill(opts.dir);
5795
+ if (installed) {
5796
+ resolved = installed.origin?.source === "shared" ? sharedToBundle(await client.getSharedSkill(installed.origin.skillId)) : { bundle: await client.getSkillBundle(installed.name), origin: { source: "platform" } };
5797
+ }
5798
+ }
5799
+ resolved = resolved ?? await fetchSkillByOrigin(client, name, opts.area);
5800
+ const bundle = validateSkillBundle(resolved.bundle);
5801
+ const dirs = resolveTargetDirs(parseTarget(opts.target), bundle.name, opts.dir);
5709
5802
  const installations = [];
5710
5803
  for (const dir of dirs) {
5711
- const result = writeSkillBundle(bundle, dir);
5804
+ const result = writeSkillBundle(bundle, dir, resolved.origin);
5712
5805
  installations.push({ directory: dir, ...result });
5713
5806
  }
5714
- output({ action: "install", bundle: bundle.name, version: bundle.version, hash: bundle.hash, installations });
5715
- });
5716
- skill.command("update [name]").description("Update the skill to the latest version").option("--dir <path>", "Custom install directory (overrides --target)").option("--target <target>", "Install target: agents, claude, or all (default: all)").action(async (name, opts) => {
5807
+ output({
5808
+ action,
5809
+ bundle: bundle.name,
5810
+ version: bundle.version,
5811
+ hash: bundle.hash,
5812
+ origin: resolved.origin.source,
5813
+ ...resolved.origin.source === "shared" ? { uri: resolved.origin.uri } : {},
5814
+ installations
5815
+ });
5816
+ }
5817
+ function collectSkillFiles(dir) {
5818
+ const root = (0, import_node_path6.resolve)(dir);
5819
+ if (!(0, import_node_fs8.existsSync)(root) || !(0, import_node_fs8.statSync)(root).isDirectory()) throw new Error("Skill directory not found");
5820
+ const files = [];
5821
+ const walk = (current, prefix) => {
5822
+ for (const entry of (0, import_node_fs8.readdirSync)(current, { withFileTypes: true })) {
5823
+ if (entry.name.startsWith(".") || /\.new(\.\d+)?$/.test(entry.name)) continue;
5824
+ if (entry.isSymbolicLink()) throw new Error("Symlinks are not allowed in a skill directory");
5825
+ const full = (0, import_node_path6.join)(current, entry.name);
5826
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
5827
+ if (entry.isDirectory()) walk(full, relativePath);
5828
+ else if (entry.isFile()) files.push({ path: relativePath, content: (0, import_node_fs8.readFileSync)(full, "utf8") });
5829
+ }
5830
+ };
5831
+ walk(root, "");
5832
+ return files.sort((left, right) => left.path.localeCompare(right.path));
5833
+ }
5834
+ skill.command("list").description("List installable named skill bundles; --shared adds skills shared with you").option("--shared", "Include skills other users shared with you").action(async (opts) => {
5717
5835
  const client = getClient();
5718
- const bundle = await client.getSkillBundle(name);
5719
- const target = parseTarget(opts.target);
5720
- const dirs = resolveTargetDirs(target, bundle.name, opts.dir);
5721
- const installations = [];
5722
- for (const dir of dirs) {
5723
- const result = writeSkillBundle(bundle, dir);
5724
- installations.push({ directory: dir, ...result });
5836
+ const listings = (await client.listSkillBundles()).map((bundle) => ({
5837
+ ...bundle,
5838
+ source: "platform"
5839
+ }));
5840
+ if (opts.shared) {
5841
+ try {
5842
+ for (const shared of await client.listSharedSkills()) {
5843
+ listings.push({
5844
+ name: shared.name,
5845
+ version: shared.version,
5846
+ hash: shared.hash,
5847
+ source: "shared",
5848
+ areaId: shared.areaId,
5849
+ areaName: shared.areaName,
5850
+ visibility: shared.visibility,
5851
+ description: shared.description,
5852
+ uri: shared.uri,
5853
+ updatedAt: shared.updatedAt
5854
+ });
5855
+ }
5856
+ } catch (error) {
5857
+ console.error(`Shared skills unavailable: ${error instanceof Error ? error.message : String(error)}`);
5858
+ }
5725
5859
  }
5726
- output({ action: "update", bundle: bundle.name, version: bundle.version, hash: bundle.hash, installations });
5860
+ output(listings.sort((left, right) => left.name.localeCompare(right.name)));
5727
5861
  });
5728
- skill.command("status [name]").description("Check if the skill is installed").option("--dir <path>", "Custom install directory (overrides --target)").option("--target <target>", "Check target: agents, claude, or all (default: all)").action(async (name, opts) => {
5862
+ skill.command("bundle <nameOrUri>").description("Fetch and verify a skill bundle (by name, or shared-skill URI) for a managed installer").action(async (nameOrUri) => {
5863
+ if (nameOrUri.includes("://")) {
5864
+ const resource = parseBrainResource(nameOrUri);
5865
+ if (resource.resourceType !== "skill" || !resource.resourceId) throw new Error("A shared skill URI is required");
5866
+ const shared = await getClient().getSharedSkill(resource.resourceId);
5867
+ if (shared.areaId !== resource.areaId) throw new Error("Server returned a mismatched area");
5868
+ output(validateSkillBundle({ name: shared.name, version: shared.version, hash: shared.hash, files: shared.files }));
5869
+ return;
5870
+ }
5871
+ output(validateSkillBundle(await getClient().getSkillBundle(nameOrUri)));
5872
+ });
5873
+ skill.command("install [name]").description("Install a skill by bundle name, shared-skill URI, or name + --area").option("--dir <path>", "Custom install directory (overrides --target)").option("--target <target>", "Install target: agents, claude, or all (default: all)").option("--area <areaId>", "Install a shared skill from this area").action(async (name, opts) => installOrUpdateSkill("install", name, opts));
5874
+ skill.command("update [name]").description("Update a skill to the latest version from its origin").option("--dir <path>", "Custom install directory (overrides --target)").option("--target <target>", "Install target: agents, claude, or all (default: all)").option("--area <areaId>", "Update a shared skill from this area").action(async (name, opts) => installOrUpdateSkill("update", name, opts));
5875
+ skill.command("publish").description("Share a skill directory to a knowledge area so teammates can install it").requiredOption("--area <areaId>", "Owning area (see: dcli brain list)").requiredOption("--dir <path>", "Skill directory containing SKILL.md").option("--name <name>", "Skill name (default: SKILL.md frontmatter, else the directory name)").option("--skill-version <version>", "Version string (default: SKILL.md frontmatter version)").option("--visibility <visibility>", "Who can discover it: area (members only), company, or global (admins; every user)").option("--description <text>", "Short description shown in listings").action(async (opts) => {
5876
+ const visibility = (opts.visibility ?? "area").toLowerCase();
5877
+ if (visibility !== "area" && visibility !== "company" && visibility !== "global") {
5878
+ throw new Error("Invalid --visibility: use area, company, or global");
5879
+ }
5880
+ const files = collectSkillFiles(opts.dir);
5881
+ const skillMd = files.find((file) => file.path === "SKILL.md");
5882
+ if (!skillMd) throw new Error("The skill directory must contain SKILL.md");
5883
+ const name = opts.name ?? skillMd.content.match(/^name:\s*"?([a-z0-9][a-z0-9-]*)"?\s*$/m)?.[1] ?? (0, import_node_path6.basename)((0, import_node_path6.resolve)(opts.dir));
5884
+ const version = opts.skillVersion ?? skillMd.content.match(/version:\s*"([^"]+)"/)?.[1] ?? "1.0.0";
5885
+ validateSkillBundle({ name, version, files });
5886
+ const result = await getClient().publishSharedSkill({
5887
+ areaId: opts.area,
5888
+ name,
5889
+ version,
5890
+ description: opts.description,
5891
+ visibility,
5892
+ files
5893
+ });
5894
+ output({ action: "publish", ...result });
5895
+ });
5896
+ skill.command("archive <uri>").description("Archive a shared skill you own (removes it from listing and install)").action(async (uri) => {
5897
+ const resource = parseBrainResource(uri);
5898
+ if (resource.resourceType !== "skill" || !resource.resourceId) throw new Error("A shared skill URI is required");
5899
+ const result = await getClient().archiveSharedSkill(resource.resourceId);
5900
+ if (result.areaId !== resource.areaId) throw new Error("Server returned a mismatched area");
5901
+ output({ action: "archive", ...result });
5902
+ });
5903
+ skill.command("status [name]").description("Check if the skill is installed; --check compares against its origin").option("--dir <path>", "Custom install directory (overrides --target)").option("--target <target>", "Check target: agents, claude, or all (default: all)").option("--check", "Also ask the server whether a newer version exists").action(async (name, opts) => {
5729
5904
  const bundleName2 = name ?? "dayofweek-platform";
5730
5905
  const target = parseTarget(opts.target);
5731
5906
  const dirs = opts.dir ? [opts.dir] : target === "all" ? [(0, import_node_path6.join)((0, import_node_os5.homedir)(), ".agents", "skills", bundleName2), (0, import_node_path6.join)((0, import_node_os5.homedir)(), ".claude", "skills", bundleName2)] : resolveTargetDirs(target, bundleName2);
5907
+ let platformBundles;
5732
5908
  const installations = [];
5733
5909
  for (const dir of dirs) {
5734
5910
  const skillPath = (0, import_node_path6.join)(dir, "SKILL.md");
@@ -5736,9 +5912,36 @@ skill.command("status [name]").description("Check if the skill is installed").op
5736
5912
  installations.push({ installed: false, directory: dir, name: bundleName2 });
5737
5913
  continue;
5738
5914
  }
5915
+ const metadata = readInstalledSkill(dir);
5739
5916
  const content = (0, import_node_fs8.readFileSync)(skillPath, "utf-8");
5740
5917
  const versionMatch = content.match(/version:\s*"([^"]+)"/);
5741
- installations.push({ installed: true, directory: dir, name: bundleName2, version: versionMatch?.[1] ?? "unknown", sha256: sha2562(content) });
5918
+ const entry = {
5919
+ installed: true,
5920
+ directory: dir,
5921
+ name: metadata?.name ?? bundleName2,
5922
+ version: metadata?.version ?? versionMatch?.[1] ?? "unknown",
5923
+ sha256: sha2562(content),
5924
+ origin: metadata?.origin?.source ?? "platform"
5925
+ };
5926
+ if (opts.check) {
5927
+ try {
5928
+ if (metadata?.origin?.source === "shared") {
5929
+ const latest = await getClient().getSharedSkill(metadata.origin.skillId);
5930
+ entry.latestVersion = latest.version;
5931
+ entry.upToDate = metadata.hash === latest.hash;
5932
+ } else {
5933
+ platformBundles ??= await getClient().listSkillBundles();
5934
+ const latest = platformBundles.find((candidate) => candidate.name === entry.name);
5935
+ if (latest) {
5936
+ entry.latestVersion = latest.version;
5937
+ entry.upToDate = metadata ? metadata.hash === latest.hash : latest.version === entry.version;
5938
+ }
5939
+ }
5940
+ } catch (error) {
5941
+ entry.checkError = error instanceof Error ? error.message : String(error);
5942
+ }
5943
+ }
5944
+ installations.push(entry);
5742
5945
  }
5743
5946
  const installed = installations.some((entry) => entry.installed);
5744
5947
  output({ installed, bundle: bundleName2, installations });
package/dist/client.d.ts CHANGED
@@ -199,6 +199,20 @@ export declare class DayOfWeekClient {
199
199
  }): Promise<any>;
200
200
  /** Area sources, metadata only, newest first — the freshness evidence. */
201
201
  listBrainSources(areaId: string): Promise<any>;
202
+ /** An area's actors (people and organizations, the Actors tab). Active only. */
203
+ listBrainActors(areaId: string): Promise<any>;
204
+ /**
205
+ * Create an actor in an area. Idempotent on (area, name): an existing active
206
+ * actor comes back with `created: false` instead of a duplicate, so imports
207
+ * can re-run safely.
208
+ */
209
+ createBrainActor(input: {
210
+ areaId: string;
211
+ name: string;
212
+ kind: "person" | "organization";
213
+ role?: string;
214
+ description?: string;
215
+ }): Promise<any>;
202
216
  /** Entities with an active customer role and no investor/partner/producer role. */
203
217
  adminCustomersOnly(): Promise<any>;
204
218
  /** Tips mailed to press@mail.dayofweek.com, read by the press-scan skill. */
@@ -333,6 +347,20 @@ export declare class DayOfWeekClient {
333
347
  version: string;
334
348
  hash: string;
335
349
  }>>;
350
+ listSharedSkills(): Promise<SharedSkillSummary[]>;
351
+ getSharedSkill(skillId: string): Promise<SharedSkillBundle>;
352
+ publishSharedSkill(input: {
353
+ areaId: string;
354
+ name: string;
355
+ version: string;
356
+ description?: string;
357
+ visibility: "area" | "company" | "global";
358
+ files: Array<{
359
+ path: string;
360
+ content: string;
361
+ }>;
362
+ }): Promise<SharedSkillSummary>;
363
+ archiveSharedSkill(skillId: string): Promise<SharedSkillSummary>;
336
364
  getSchema(): Promise<any>;
337
365
  private get;
338
366
  private post;
@@ -399,3 +427,26 @@ export type BrainResolvedResource = {
399
427
  resourceType: "source";
400
428
  source: BrainSource;
401
429
  };
430
+ export type SharedSkillSummary = {
431
+ id: string;
432
+ areaId: string;
433
+ areaName: string;
434
+ name: string;
435
+ version: string;
436
+ description?: string;
437
+ hash: string;
438
+ visibility: "area" | "company" | "global";
439
+ revision: number;
440
+ fileCount: number;
441
+ byteSize: number;
442
+ uri: string;
443
+ httpsUrl: string;
444
+ updatedAt: number;
445
+ };
446
+ export type SharedSkillBundle = SharedSkillSummary & {
447
+ files: Array<{
448
+ path: string;
449
+ content: string;
450
+ sha256: string;
451
+ }>;
452
+ };
package/dist/client.js CHANGED
@@ -332,6 +332,18 @@ export class DayOfWeekClient {
332
332
  async listBrainSources(areaId) {
333
333
  return this.get(`/brain/sources?area=${encodeURIComponent(areaId)}`);
334
334
  }
335
+ /** An area's actors (people and organizations, the Actors tab). Active only. */
336
+ async listBrainActors(areaId) {
337
+ return this.get(`/brain/actors?area=${encodeURIComponent(areaId)}`);
338
+ }
339
+ /**
340
+ * Create an actor in an area. Idempotent on (area, name): an existing active
341
+ * actor comes back with `created: false` instead of a duplicate, so imports
342
+ * can re-run safely.
343
+ */
344
+ async createBrainActor(input) {
345
+ return this.post("/brain/actors", input);
346
+ }
335
347
  /** Entities with an active customer role and no investor/partner/producer role. */
336
348
  async adminCustomersOnly() {
337
349
  return this.get("/admin/customers-only");
@@ -512,6 +524,23 @@ export class DayOfWeekClient {
512
524
  async listSkillBundles() {
513
525
  return this.get("/skill?list=1");
514
526
  }
527
+ // ── Shared skills ─────────────────────────────────────────────────────────
528
+ //
529
+ // Skills other users published into shared knowledge areas. Discovery is
530
+ // server-owned, like the named bundles above: the CLI ships no skill names,
531
+ // and the server decides which skills this device's user may see.
532
+ async listSharedSkills() {
533
+ return this.get("/brain/skills");
534
+ }
535
+ async getSharedSkill(skillId) {
536
+ return this.get(`/brain/skills/${encodeURIComponent(skillId)}`);
537
+ }
538
+ async publishSharedSkill(input) {
539
+ return this.post("/brain/skills", input);
540
+ }
541
+ async archiveSharedSkill(skillId) {
542
+ return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
543
+ }
515
544
  // ── Schema ────────────────────────────────────────────────────────────────
516
545
  async getSchema() {
517
546
  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,7 +1,7 @@
1
1
  {
2
2
  "name": "@dayofweek/dcli",
3
- "version": "1.6.0",
4
- "description": "CLI for the Day of Week AgTech platform read data and submit proposals for review",
3
+ "version": "1.8.0",
4
+ "description": "CLI for the Day of Week AgTech platform \u2014 read data and submit proposals for review",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {