@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 +28 -0
- package/dist/bin/dcli.js +249 -33
- package/dist/bundle/dcli.cjs +227 -24
- package/dist/client.d.ts +51 -0
- package/dist/client.js +29 -0
- package/dist/skills.d.ts +24 -1
- package/dist/skills.js +17 -2
- package/dist/uri.d.ts +1 -1
- package/dist/uri.js +1 -1
- package/package.json +2 -2
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
|
|
774
|
-
.
|
|
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 <
|
|
777
|
-
.description("Fetch and verify a
|
|
778
|
-
.action(async (
|
|
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
|
|
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
|
-
.
|
|
785
|
-
|
|
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
|
|
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
|
-
.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 });
|
package/dist/bundle/dcli.cjs
CHANGED
|
@@ -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.
|
|
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)(
|
|
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
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
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
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
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({
|
|
5715
|
-
|
|
5716
|
-
|
|
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
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
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(
|
|
5860
|
+
output(listings.sort((left, right) => left.name.localeCompare(right.name)));
|
|
5727
5861
|
});
|
|
5728
|
-
skill.command("
|
|
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
|
-
|
|
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
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.
|
|
4
|
-
"description": "CLI for the Day of Week AgTech platform
|
|
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": {
|