@dayofweek/dcli 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/bin/dcli.js +219 -33
- package/dist/bundle/dcli.cjs +199 -24
- package/dist/client.d.ts +37 -0
- package/dist/client.js +17 -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 +1 -1
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")
|
|
@@ -768,53 +768,207 @@ function parseTarget(value) {
|
|
|
768
768
|
}
|
|
769
769
|
return v;
|
|
770
770
|
}
|
|
771
|
+
function sharedToBundle(shared) {
|
|
772
|
+
return {
|
|
773
|
+
bundle: { name: shared.name, version: shared.version, hash: shared.hash, files: shared.files },
|
|
774
|
+
origin: { source: "shared", skillId: shared.id, areaId: shared.areaId, uri: shared.uri },
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
async function fetchSkillByOrigin(client, nameOrUri, areaId) {
|
|
778
|
+
if (nameOrUri?.includes("://")) {
|
|
779
|
+
const resource = parseBrainResource(nameOrUri);
|
|
780
|
+
if (resource.resourceType !== "skill" || !resource.resourceId)
|
|
781
|
+
throw new Error("A shared skill URI is required");
|
|
782
|
+
const shared = await client.getSharedSkill(resource.resourceId);
|
|
783
|
+
if (shared.areaId !== resource.areaId)
|
|
784
|
+
throw new Error("Server returned a mismatched area");
|
|
785
|
+
return sharedToBundle(shared);
|
|
786
|
+
}
|
|
787
|
+
if (areaId) {
|
|
788
|
+
if (!nameOrUri)
|
|
789
|
+
throw new Error("A skill name is required together with --area");
|
|
790
|
+
const match = (await client.listSharedSkills()).find((candidate) => candidate.areaId === areaId && candidate.name === nameOrUri);
|
|
791
|
+
if (!match)
|
|
792
|
+
throw new Error("Shared skill not found in that area");
|
|
793
|
+
return sharedToBundle(await client.getSharedSkill(match.id));
|
|
794
|
+
}
|
|
795
|
+
return { bundle: await client.getSkillBundle(nameOrUri), origin: { source: "platform" } };
|
|
796
|
+
}
|
|
797
|
+
async function installOrUpdateSkill(action, name, opts) {
|
|
798
|
+
const client = getClient();
|
|
799
|
+
let resolved;
|
|
800
|
+
if (action === "update" && !name && !opts.area && opts.dir) {
|
|
801
|
+
// Bare `skill update --dir …`: go back to wherever this install came from.
|
|
802
|
+
const installed = readInstalledSkill(opts.dir);
|
|
803
|
+
if (installed) {
|
|
804
|
+
resolved = installed.origin?.source === "shared"
|
|
805
|
+
? sharedToBundle(await client.getSharedSkill(installed.origin.skillId))
|
|
806
|
+
: { bundle: await client.getSkillBundle(installed.name), origin: { source: "platform" } };
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
resolved = resolved ?? await fetchSkillByOrigin(client, name, opts.area);
|
|
810
|
+
const bundle = validateSkillBundle(resolved.bundle);
|
|
811
|
+
const dirs = resolveTargetDirs(parseTarget(opts.target), bundle.name, opts.dir);
|
|
812
|
+
const installations = [];
|
|
813
|
+
for (const dir of dirs) {
|
|
814
|
+
const result = writeSkillBundle(bundle, dir, resolved.origin);
|
|
815
|
+
installations.push({ directory: dir, ...result });
|
|
816
|
+
}
|
|
817
|
+
output({
|
|
818
|
+
action,
|
|
819
|
+
bundle: bundle.name,
|
|
820
|
+
version: bundle.version,
|
|
821
|
+
hash: bundle.hash,
|
|
822
|
+
origin: resolved.origin.source,
|
|
823
|
+
...(resolved.origin.source === "shared" ? { uri: resolved.origin.uri } : {}),
|
|
824
|
+
installations,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
/** Read a skill directory into bundle files: relative paths, utf-8 content. */
|
|
828
|
+
function collectSkillFiles(dir) {
|
|
829
|
+
const root = resolve(dir);
|
|
830
|
+
if (!existsSync(root) || !statSync(root).isDirectory())
|
|
831
|
+
throw new Error("Skill directory not found");
|
|
832
|
+
const files = [];
|
|
833
|
+
const walk = (current, prefix) => {
|
|
834
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
835
|
+
// Hidden entries (including the .dayofweek-skill.json install manifest)
|
|
836
|
+
// and update-conflict artifacts never belong in a published bundle.
|
|
837
|
+
if (entry.name.startsWith(".") || /\.new(\.\d+)?$/.test(entry.name))
|
|
838
|
+
continue;
|
|
839
|
+
if (entry.isSymbolicLink())
|
|
840
|
+
throw new Error("Symlinks are not allowed in a skill directory");
|
|
841
|
+
const full = join(current, entry.name);
|
|
842
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
843
|
+
if (entry.isDirectory())
|
|
844
|
+
walk(full, relativePath);
|
|
845
|
+
else if (entry.isFile())
|
|
846
|
+
files.push({ path: relativePath, content: readFileSync(full, "utf8") });
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
walk(root, "");
|
|
850
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
851
|
+
}
|
|
771
852
|
skill
|
|
772
853
|
.command("list")
|
|
773
|
-
.description("List
|
|
774
|
-
.
|
|
854
|
+
.description("List installable named skill bundles; --shared adds skills shared with you")
|
|
855
|
+
.option("--shared", "Include skills other users shared with you")
|
|
856
|
+
.action(async (opts) => {
|
|
857
|
+
const client = getClient();
|
|
858
|
+
// The bare listing stays exactly the named bundles: managed installers
|
|
859
|
+
// iterate it and fetch every entry by plain name, so shared skills (which
|
|
860
|
+
// resolve by URI) only appear when explicitly asked for.
|
|
861
|
+
const listings = (await client.listSkillBundles()).map((bundle) => ({
|
|
862
|
+
...bundle,
|
|
863
|
+
source: "platform",
|
|
864
|
+
}));
|
|
865
|
+
if (opts.shared) {
|
|
866
|
+
try {
|
|
867
|
+
for (const shared of await client.listSharedSkills()) {
|
|
868
|
+
listings.push({
|
|
869
|
+
name: shared.name,
|
|
870
|
+
version: shared.version,
|
|
871
|
+
hash: shared.hash,
|
|
872
|
+
source: "shared",
|
|
873
|
+
areaId: shared.areaId,
|
|
874
|
+
areaName: shared.areaName,
|
|
875
|
+
visibility: shared.visibility,
|
|
876
|
+
description: shared.description,
|
|
877
|
+
uri: shared.uri,
|
|
878
|
+
updatedAt: shared.updatedAt,
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
catch (error) {
|
|
883
|
+
// Older servers or tokens without knowledge scopes have no shared-skill
|
|
884
|
+
// surface — the named bundles are still worth listing.
|
|
885
|
+
console.error(`Shared skills unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
output(listings.sort((left, right) => left.name.localeCompare(right.name)));
|
|
889
|
+
});
|
|
775
890
|
skill
|
|
776
|
-
.command("bundle <
|
|
777
|
-
.description("Fetch and verify a
|
|
778
|
-
.action(async (
|
|
891
|
+
.command("bundle <nameOrUri>")
|
|
892
|
+
.description("Fetch and verify a skill bundle (by name, or shared-skill URI) for a managed installer")
|
|
893
|
+
.action(async (nameOrUri) => {
|
|
894
|
+
if (nameOrUri.includes("://")) {
|
|
895
|
+
const resource = parseBrainResource(nameOrUri);
|
|
896
|
+
if (resource.resourceType !== "skill" || !resource.resourceId)
|
|
897
|
+
throw new Error("A shared skill URI is required");
|
|
898
|
+
const shared = await getClient().getSharedSkill(resource.resourceId);
|
|
899
|
+
if (shared.areaId !== resource.areaId)
|
|
900
|
+
throw new Error("Server returned a mismatched area");
|
|
901
|
+
// Emit the plain bundle shape managed installers expect.
|
|
902
|
+
output(validateSkillBundle({ name: shared.name, version: shared.version, hash: shared.hash, files: shared.files }));
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
output(validateSkillBundle(await getClient().getSkillBundle(nameOrUri)));
|
|
906
|
+
});
|
|
779
907
|
skill
|
|
780
908
|
.command("install [name]")
|
|
781
|
-
.description("Install
|
|
909
|
+
.description("Install a skill by bundle name, shared-skill URI, or name + --area")
|
|
782
910
|
.option("--dir <path>", "Custom install directory (overrides --target)")
|
|
783
911
|
.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
|
-
});
|
|
912
|
+
.option("--area <areaId>", "Install a shared skill from this area")
|
|
913
|
+
.action(async (name, opts) => installOrUpdateSkill("install", name, opts));
|
|
796
914
|
skill
|
|
797
915
|
.command("update [name]")
|
|
798
|
-
.description("Update
|
|
916
|
+
.description("Update a skill to the latest version from its origin")
|
|
799
917
|
.option("--dir <path>", "Custom install directory (overrides --target)")
|
|
800
918
|
.option("--target <target>", "Install target: agents, claude, or all (default: all)")
|
|
801
|
-
.
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
919
|
+
.option("--area <areaId>", "Update a shared skill from this area")
|
|
920
|
+
.action(async (name, opts) => installOrUpdateSkill("update", name, opts));
|
|
921
|
+
skill
|
|
922
|
+
.command("publish")
|
|
923
|
+
.description("Share a skill directory to a knowledge area so teammates can install it")
|
|
924
|
+
.requiredOption("--area <areaId>", "Owning area (see: dcli brain list)")
|
|
925
|
+
.requiredOption("--dir <path>", "Skill directory containing SKILL.md")
|
|
926
|
+
.option("--name <name>", "Skill name (default: SKILL.md frontmatter, else the directory name)")
|
|
927
|
+
.option("--skill-version <version>", "Version string (default: SKILL.md frontmatter version)")
|
|
928
|
+
.option("--visibility <visibility>", "Who can discover it: area (members only), company, or global (admins; every user)")
|
|
929
|
+
.option("--description <text>", "Short description shown in listings")
|
|
930
|
+
.action(async (opts) => {
|
|
931
|
+
const visibility = (opts.visibility ?? "area").toLowerCase();
|
|
932
|
+
if (visibility !== "area" && visibility !== "company" && visibility !== "global") {
|
|
933
|
+
throw new Error("Invalid --visibility: use area, company, or global");
|
|
810
934
|
}
|
|
811
|
-
|
|
935
|
+
const files = collectSkillFiles(opts.dir);
|
|
936
|
+
const skillMd = files.find((file) => file.path === "SKILL.md");
|
|
937
|
+
if (!skillMd)
|
|
938
|
+
throw new Error("The skill directory must contain SKILL.md");
|
|
939
|
+
const name = opts.name
|
|
940
|
+
?? skillMd.content.match(/^name:\s*"?([a-z0-9][a-z0-9-]*)"?\s*$/m)?.[1]
|
|
941
|
+
?? basename(resolve(opts.dir));
|
|
942
|
+
const version = opts.skillVersion ?? skillMd.content.match(/version:\s*"([^"]+)"/)?.[1] ?? "1.0.0";
|
|
943
|
+
validateSkillBundle({ name, version, files });
|
|
944
|
+
const result = await getClient().publishSharedSkill({
|
|
945
|
+
areaId: opts.area,
|
|
946
|
+
name,
|
|
947
|
+
version,
|
|
948
|
+
description: opts.description,
|
|
949
|
+
visibility,
|
|
950
|
+
files,
|
|
951
|
+
});
|
|
952
|
+
output({ action: "publish", ...result });
|
|
953
|
+
});
|
|
954
|
+
skill
|
|
955
|
+
.command("archive <uri>")
|
|
956
|
+
.description("Archive a shared skill you own (removes it from listing and install)")
|
|
957
|
+
.action(async (uri) => {
|
|
958
|
+
const resource = parseBrainResource(uri);
|
|
959
|
+
if (resource.resourceType !== "skill" || !resource.resourceId)
|
|
960
|
+
throw new Error("A shared skill URI is required");
|
|
961
|
+
const result = await getClient().archiveSharedSkill(resource.resourceId);
|
|
962
|
+
if (result.areaId !== resource.areaId)
|
|
963
|
+
throw new Error("Server returned a mismatched area");
|
|
964
|
+
output({ action: "archive", ...result });
|
|
812
965
|
});
|
|
813
966
|
skill
|
|
814
967
|
.command("status [name]")
|
|
815
|
-
.description("Check if the skill is installed")
|
|
968
|
+
.description("Check if the skill is installed; --check compares against its origin")
|
|
816
969
|
.option("--dir <path>", "Custom install directory (overrides --target)")
|
|
817
970
|
.option("--target <target>", "Check target: agents, claude, or all (default: all)")
|
|
971
|
+
.option("--check", "Also ask the server whether a newer version exists")
|
|
818
972
|
.action(async (name, opts) => {
|
|
819
973
|
const bundleName = name ?? "dayofweek-platform";
|
|
820
974
|
const target = parseTarget(opts.target);
|
|
@@ -823,6 +977,7 @@ skill
|
|
|
823
977
|
: target === "all"
|
|
824
978
|
? [join(homedir(), ".agents", "skills", bundleName), join(homedir(), ".claude", "skills", bundleName)]
|
|
825
979
|
: resolveTargetDirs(target, bundleName);
|
|
980
|
+
let platformBundles;
|
|
826
981
|
const installations = [];
|
|
827
982
|
for (const dir of dirs) {
|
|
828
983
|
const skillPath = join(dir, "SKILL.md");
|
|
@@ -830,9 +985,40 @@ skill
|
|
|
830
985
|
installations.push({ installed: false, directory: dir, name: bundleName });
|
|
831
986
|
continue;
|
|
832
987
|
}
|
|
988
|
+
const metadata = readInstalledSkill(dir);
|
|
833
989
|
const content = readFileSync(skillPath, "utf-8");
|
|
834
990
|
const versionMatch = content.match(/version:\s*"([^"]+)"/);
|
|
835
|
-
|
|
991
|
+
const entry = {
|
|
992
|
+
installed: true,
|
|
993
|
+
directory: dir,
|
|
994
|
+
name: metadata?.name ?? bundleName,
|
|
995
|
+
version: metadata?.version ?? versionMatch?.[1] ?? "unknown",
|
|
996
|
+
sha256: sha256(content),
|
|
997
|
+
origin: metadata?.origin?.source ?? "platform",
|
|
998
|
+
};
|
|
999
|
+
if (opts.check) {
|
|
1000
|
+
try {
|
|
1001
|
+
if (metadata?.origin?.source === "shared") {
|
|
1002
|
+
const latest = await getClient().getSharedSkill(metadata.origin.skillId);
|
|
1003
|
+
entry.latestVersion = latest.version;
|
|
1004
|
+
entry.upToDate = metadata.hash === latest.hash;
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
platformBundles ??= await getClient().listSkillBundles();
|
|
1008
|
+
const latest = platformBundles.find((candidate) => candidate.name === entry.name);
|
|
1009
|
+
if (latest) {
|
|
1010
|
+
entry.latestVersion = latest.version;
|
|
1011
|
+
// Pre-origin installs have no recorded manifest hash; fall back
|
|
1012
|
+
// to comparing the declared versions.
|
|
1013
|
+
entry.upToDate = metadata ? metadata.hash === latest.hash : latest.version === entry.version;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
catch (error) {
|
|
1018
|
+
entry.checkError = error instanceof Error ? error.message : String(error);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
installations.push(entry);
|
|
836
1022
|
}
|
|
837
1023
|
const installed = installations.some((entry) => entry.installed);
|
|
838
1024
|
output({ installed, bundle: bundleName, installations });
|
package/dist/bundle/dcli.cjs
CHANGED
|
@@ -4633,6 +4633,23 @@ var DayOfWeekClient = class {
|
|
|
4633
4633
|
async listSkillBundles() {
|
|
4634
4634
|
return this.get("/skill?list=1");
|
|
4635
4635
|
}
|
|
4636
|
+
// ── Shared skills ─────────────────────────────────────────────────────────
|
|
4637
|
+
//
|
|
4638
|
+
// Skills other users published into shared knowledge areas. Discovery is
|
|
4639
|
+
// server-owned, like the named bundles above: the CLI ships no skill names,
|
|
4640
|
+
// and the server decides which skills this device's user may see.
|
|
4641
|
+
async listSharedSkills() {
|
|
4642
|
+
return this.get("/brain/skills");
|
|
4643
|
+
}
|
|
4644
|
+
async getSharedSkill(skillId) {
|
|
4645
|
+
return this.get(`/brain/skills/${encodeURIComponent(skillId)}`);
|
|
4646
|
+
}
|
|
4647
|
+
async publishSharedSkill(input) {
|
|
4648
|
+
return this.post("/brain/skills", input);
|
|
4649
|
+
}
|
|
4650
|
+
async archiveSharedSkill(skillId) {
|
|
4651
|
+
return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
|
|
4652
|
+
}
|
|
4636
4653
|
// ── Schema ────────────────────────────────────────────────────────────────
|
|
4637
4654
|
async getSchema() {
|
|
4638
4655
|
return this.get("/schema");
|
|
@@ -5037,7 +5054,7 @@ function parseSegments(pathname) {
|
|
|
5037
5054
|
if (segments.length !== 3) throw new Error("Unsupported brain URI form");
|
|
5038
5055
|
const areaId = assertId(segments[0], "area ID");
|
|
5039
5056
|
const resourceType = segments[1];
|
|
5040
|
-
if (resourceType !== "note" && resourceType !== "source") {
|
|
5057
|
+
if (resourceType !== "note" && resourceType !== "source" && resourceType !== "skill") {
|
|
5041
5058
|
throw new Error("Unsupported brain resource type");
|
|
5042
5059
|
}
|
|
5043
5060
|
return {
|
|
@@ -5081,7 +5098,7 @@ var import_promises4 = require("node:readline/promises");
|
|
|
5081
5098
|
// package.json
|
|
5082
5099
|
var package_default = {
|
|
5083
5100
|
name: "@dayofweek/dcli",
|
|
5084
|
-
version: "1.
|
|
5101
|
+
version: "1.7.0",
|
|
5085
5102
|
description: "CLI for the Day of Week AgTech platform \u2014 read data and submit proposals for review",
|
|
5086
5103
|
license: "MIT",
|
|
5087
5104
|
type: "module",
|
|
@@ -5141,6 +5158,17 @@ var import_node_crypto4 = require("node:crypto");
|
|
|
5141
5158
|
var import_node_crypto3 = require("node:crypto");
|
|
5142
5159
|
var import_node_fs7 = require("node:fs");
|
|
5143
5160
|
var import_node_path5 = require("node:path");
|
|
5161
|
+
function readInstalledSkill(targetDir) {
|
|
5162
|
+
const metadataPath = (0, import_node_path5.join)(targetDir, ".dayofweek-skill.json");
|
|
5163
|
+
if (!(0, import_node_fs7.existsSync)(metadataPath)) return null;
|
|
5164
|
+
try {
|
|
5165
|
+
const parsed = JSON.parse((0, import_node_fs7.readFileSync)(metadataPath, "utf8"));
|
|
5166
|
+
if (typeof parsed.name !== "string" || typeof parsed.version !== "string") return null;
|
|
5167
|
+
return parsed;
|
|
5168
|
+
} catch {
|
|
5169
|
+
return null;
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5144
5172
|
function sha256(value) {
|
|
5145
5173
|
return (0, import_node_crypto3.createHash)("sha256").update(value).digest("hex");
|
|
5146
5174
|
}
|
|
@@ -5164,7 +5192,7 @@ function validateSkillBundle(bundle) {
|
|
|
5164
5192
|
if (bundle.hash && bundle.hash !== actualBundleHash) throw new Error("Skill bundle manifest checksum mismatch");
|
|
5165
5193
|
return { ...bundle, hash: actualBundleHash, files };
|
|
5166
5194
|
}
|
|
5167
|
-
function writeSkillBundle(input, targetDir) {
|
|
5195
|
+
function writeSkillBundle(input, targetDir, origin) {
|
|
5168
5196
|
const bundle = validateSkillBundle(input);
|
|
5169
5197
|
let filesWritten = 0;
|
|
5170
5198
|
let unchanged = 0;
|
|
@@ -5200,7 +5228,15 @@ function writeSkillBundle(input, targetDir) {
|
|
|
5200
5228
|
filesWritten++;
|
|
5201
5229
|
}
|
|
5202
5230
|
(0, import_node_fs7.mkdirSync)(targetDir, { recursive: true });
|
|
5203
|
-
(0, import_node_fs7.writeFileSync)(
|
|
5231
|
+
(0, import_node_fs7.writeFileSync)(
|
|
5232
|
+
metadataPath,
|
|
5233
|
+
JSON.stringify(
|
|
5234
|
+
{ name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes, ...origin ? { origin } : {} },
|
|
5235
|
+
null,
|
|
5236
|
+
2
|
|
5237
|
+
),
|
|
5238
|
+
"utf8"
|
|
5239
|
+
);
|
|
5204
5240
|
return { written: filesWritten, unchanged, conflicts };
|
|
5205
5241
|
}
|
|
5206
5242
|
|
|
@@ -5699,36 +5735,148 @@ function parseTarget(value) {
|
|
|
5699
5735
|
}
|
|
5700
5736
|
return v;
|
|
5701
5737
|
}
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5738
|
+
function sharedToBundle(shared) {
|
|
5739
|
+
return {
|
|
5740
|
+
bundle: { name: shared.name, version: shared.version, hash: shared.hash, files: shared.files },
|
|
5741
|
+
origin: { source: "shared", skillId: shared.id, areaId: shared.areaId, uri: shared.uri }
|
|
5742
|
+
};
|
|
5743
|
+
}
|
|
5744
|
+
async function fetchSkillByOrigin(client, nameOrUri, areaId) {
|
|
5745
|
+
if (nameOrUri?.includes("://")) {
|
|
5746
|
+
const resource = parseBrainResource(nameOrUri);
|
|
5747
|
+
if (resource.resourceType !== "skill" || !resource.resourceId) throw new Error("A shared skill URI is required");
|
|
5748
|
+
const shared = await client.getSharedSkill(resource.resourceId);
|
|
5749
|
+
if (shared.areaId !== resource.areaId) throw new Error("Server returned a mismatched area");
|
|
5750
|
+
return sharedToBundle(shared);
|
|
5751
|
+
}
|
|
5752
|
+
if (areaId) {
|
|
5753
|
+
if (!nameOrUri) throw new Error("A skill name is required together with --area");
|
|
5754
|
+
const match = (await client.listSharedSkills()).find(
|
|
5755
|
+
(candidate) => candidate.areaId === areaId && candidate.name === nameOrUri
|
|
5756
|
+
);
|
|
5757
|
+
if (!match) throw new Error("Shared skill not found in that area");
|
|
5758
|
+
return sharedToBundle(await client.getSharedSkill(match.id));
|
|
5759
|
+
}
|
|
5760
|
+
return { bundle: await client.getSkillBundle(nameOrUri), origin: { source: "platform" } };
|
|
5761
|
+
}
|
|
5762
|
+
async function installOrUpdateSkill(action, name, opts) {
|
|
5705
5763
|
const client = getClient();
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5764
|
+
let resolved;
|
|
5765
|
+
if (action === "update" && !name && !opts.area && opts.dir) {
|
|
5766
|
+
const installed = readInstalledSkill(opts.dir);
|
|
5767
|
+
if (installed) {
|
|
5768
|
+
resolved = installed.origin?.source === "shared" ? sharedToBundle(await client.getSharedSkill(installed.origin.skillId)) : { bundle: await client.getSkillBundle(installed.name), origin: { source: "platform" } };
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5771
|
+
resolved = resolved ?? await fetchSkillByOrigin(client, name, opts.area);
|
|
5772
|
+
const bundle = validateSkillBundle(resolved.bundle);
|
|
5773
|
+
const dirs = resolveTargetDirs(parseTarget(opts.target), bundle.name, opts.dir);
|
|
5709
5774
|
const installations = [];
|
|
5710
5775
|
for (const dir of dirs) {
|
|
5711
|
-
const result = writeSkillBundle(bundle, dir);
|
|
5776
|
+
const result = writeSkillBundle(bundle, dir, resolved.origin);
|
|
5712
5777
|
installations.push({ directory: dir, ...result });
|
|
5713
5778
|
}
|
|
5714
|
-
output({
|
|
5715
|
-
|
|
5716
|
-
|
|
5779
|
+
output({
|
|
5780
|
+
action,
|
|
5781
|
+
bundle: bundle.name,
|
|
5782
|
+
version: bundle.version,
|
|
5783
|
+
hash: bundle.hash,
|
|
5784
|
+
origin: resolved.origin.source,
|
|
5785
|
+
...resolved.origin.source === "shared" ? { uri: resolved.origin.uri } : {},
|
|
5786
|
+
installations
|
|
5787
|
+
});
|
|
5788
|
+
}
|
|
5789
|
+
function collectSkillFiles(dir) {
|
|
5790
|
+
const root = (0, import_node_path6.resolve)(dir);
|
|
5791
|
+
if (!(0, import_node_fs8.existsSync)(root) || !(0, import_node_fs8.statSync)(root).isDirectory()) throw new Error("Skill directory not found");
|
|
5792
|
+
const files = [];
|
|
5793
|
+
const walk = (current, prefix) => {
|
|
5794
|
+
for (const entry of (0, import_node_fs8.readdirSync)(current, { withFileTypes: true })) {
|
|
5795
|
+
if (entry.name.startsWith(".") || /\.new(\.\d+)?$/.test(entry.name)) continue;
|
|
5796
|
+
if (entry.isSymbolicLink()) throw new Error("Symlinks are not allowed in a skill directory");
|
|
5797
|
+
const full = (0, import_node_path6.join)(current, entry.name);
|
|
5798
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
5799
|
+
if (entry.isDirectory()) walk(full, relativePath);
|
|
5800
|
+
else if (entry.isFile()) files.push({ path: relativePath, content: (0, import_node_fs8.readFileSync)(full, "utf8") });
|
|
5801
|
+
}
|
|
5802
|
+
};
|
|
5803
|
+
walk(root, "");
|
|
5804
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
5805
|
+
}
|
|
5806
|
+
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
5807
|
const client = getClient();
|
|
5718
|
-
const
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5808
|
+
const listings = (await client.listSkillBundles()).map((bundle) => ({
|
|
5809
|
+
...bundle,
|
|
5810
|
+
source: "platform"
|
|
5811
|
+
}));
|
|
5812
|
+
if (opts.shared) {
|
|
5813
|
+
try {
|
|
5814
|
+
for (const shared of await client.listSharedSkills()) {
|
|
5815
|
+
listings.push({
|
|
5816
|
+
name: shared.name,
|
|
5817
|
+
version: shared.version,
|
|
5818
|
+
hash: shared.hash,
|
|
5819
|
+
source: "shared",
|
|
5820
|
+
areaId: shared.areaId,
|
|
5821
|
+
areaName: shared.areaName,
|
|
5822
|
+
visibility: shared.visibility,
|
|
5823
|
+
description: shared.description,
|
|
5824
|
+
uri: shared.uri,
|
|
5825
|
+
updatedAt: shared.updatedAt
|
|
5826
|
+
});
|
|
5827
|
+
}
|
|
5828
|
+
} catch (error) {
|
|
5829
|
+
console.error(`Shared skills unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
output(listings.sort((left, right) => left.name.localeCompare(right.name)));
|
|
5833
|
+
});
|
|
5834
|
+
skill.command("bundle <nameOrUri>").description("Fetch and verify a skill bundle (by name, or shared-skill URI) for a managed installer").action(async (nameOrUri) => {
|
|
5835
|
+
if (nameOrUri.includes("://")) {
|
|
5836
|
+
const resource = parseBrainResource(nameOrUri);
|
|
5837
|
+
if (resource.resourceType !== "skill" || !resource.resourceId) throw new Error("A shared skill URI is required");
|
|
5838
|
+
const shared = await getClient().getSharedSkill(resource.resourceId);
|
|
5839
|
+
if (shared.areaId !== resource.areaId) throw new Error("Server returned a mismatched area");
|
|
5840
|
+
output(validateSkillBundle({ name: shared.name, version: shared.version, hash: shared.hash, files: shared.files }));
|
|
5841
|
+
return;
|
|
5725
5842
|
}
|
|
5726
|
-
output(
|
|
5843
|
+
output(validateSkillBundle(await getClient().getSkillBundle(nameOrUri)));
|
|
5727
5844
|
});
|
|
5728
|
-
skill.command("
|
|
5845
|
+
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));
|
|
5846
|
+
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));
|
|
5847
|
+
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) => {
|
|
5848
|
+
const visibility = (opts.visibility ?? "area").toLowerCase();
|
|
5849
|
+
if (visibility !== "area" && visibility !== "company" && visibility !== "global") {
|
|
5850
|
+
throw new Error("Invalid --visibility: use area, company, or global");
|
|
5851
|
+
}
|
|
5852
|
+
const files = collectSkillFiles(opts.dir);
|
|
5853
|
+
const skillMd = files.find((file) => file.path === "SKILL.md");
|
|
5854
|
+
if (!skillMd) throw new Error("The skill directory must contain SKILL.md");
|
|
5855
|
+
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));
|
|
5856
|
+
const version = opts.skillVersion ?? skillMd.content.match(/version:\s*"([^"]+)"/)?.[1] ?? "1.0.0";
|
|
5857
|
+
validateSkillBundle({ name, version, files });
|
|
5858
|
+
const result = await getClient().publishSharedSkill({
|
|
5859
|
+
areaId: opts.area,
|
|
5860
|
+
name,
|
|
5861
|
+
version,
|
|
5862
|
+
description: opts.description,
|
|
5863
|
+
visibility,
|
|
5864
|
+
files
|
|
5865
|
+
});
|
|
5866
|
+
output({ action: "publish", ...result });
|
|
5867
|
+
});
|
|
5868
|
+
skill.command("archive <uri>").description("Archive a shared skill you own (removes it from listing and install)").action(async (uri) => {
|
|
5869
|
+
const resource = parseBrainResource(uri);
|
|
5870
|
+
if (resource.resourceType !== "skill" || !resource.resourceId) throw new Error("A shared skill URI is required");
|
|
5871
|
+
const result = await getClient().archiveSharedSkill(resource.resourceId);
|
|
5872
|
+
if (result.areaId !== resource.areaId) throw new Error("Server returned a mismatched area");
|
|
5873
|
+
output({ action: "archive", ...result });
|
|
5874
|
+
});
|
|
5875
|
+
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
5876
|
const bundleName2 = name ?? "dayofweek-platform";
|
|
5730
5877
|
const target = parseTarget(opts.target);
|
|
5731
5878
|
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);
|
|
5879
|
+
let platformBundles;
|
|
5732
5880
|
const installations = [];
|
|
5733
5881
|
for (const dir of dirs) {
|
|
5734
5882
|
const skillPath = (0, import_node_path6.join)(dir, "SKILL.md");
|
|
@@ -5736,9 +5884,36 @@ skill.command("status [name]").description("Check if the skill is installed").op
|
|
|
5736
5884
|
installations.push({ installed: false, directory: dir, name: bundleName2 });
|
|
5737
5885
|
continue;
|
|
5738
5886
|
}
|
|
5887
|
+
const metadata = readInstalledSkill(dir);
|
|
5739
5888
|
const content = (0, import_node_fs8.readFileSync)(skillPath, "utf-8");
|
|
5740
5889
|
const versionMatch = content.match(/version:\s*"([^"]+)"/);
|
|
5741
|
-
|
|
5890
|
+
const entry = {
|
|
5891
|
+
installed: true,
|
|
5892
|
+
directory: dir,
|
|
5893
|
+
name: metadata?.name ?? bundleName2,
|
|
5894
|
+
version: metadata?.version ?? versionMatch?.[1] ?? "unknown",
|
|
5895
|
+
sha256: sha2562(content),
|
|
5896
|
+
origin: metadata?.origin?.source ?? "platform"
|
|
5897
|
+
};
|
|
5898
|
+
if (opts.check) {
|
|
5899
|
+
try {
|
|
5900
|
+
if (metadata?.origin?.source === "shared") {
|
|
5901
|
+
const latest = await getClient().getSharedSkill(metadata.origin.skillId);
|
|
5902
|
+
entry.latestVersion = latest.version;
|
|
5903
|
+
entry.upToDate = metadata.hash === latest.hash;
|
|
5904
|
+
} else {
|
|
5905
|
+
platformBundles ??= await getClient().listSkillBundles();
|
|
5906
|
+
const latest = platformBundles.find((candidate) => candidate.name === entry.name);
|
|
5907
|
+
if (latest) {
|
|
5908
|
+
entry.latestVersion = latest.version;
|
|
5909
|
+
entry.upToDate = metadata ? metadata.hash === latest.hash : latest.version === entry.version;
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
} catch (error) {
|
|
5913
|
+
entry.checkError = error instanceof Error ? error.message : String(error);
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
installations.push(entry);
|
|
5742
5917
|
}
|
|
5743
5918
|
const installed = installations.some((entry) => entry.installed);
|
|
5744
5919
|
output({ installed, bundle: bundleName2, installations });
|
package/dist/client.d.ts
CHANGED
|
@@ -333,6 +333,20 @@ export declare class DayOfWeekClient {
|
|
|
333
333
|
version: string;
|
|
334
334
|
hash: string;
|
|
335
335
|
}>>;
|
|
336
|
+
listSharedSkills(): Promise<SharedSkillSummary[]>;
|
|
337
|
+
getSharedSkill(skillId: string): Promise<SharedSkillBundle>;
|
|
338
|
+
publishSharedSkill(input: {
|
|
339
|
+
areaId: string;
|
|
340
|
+
name: string;
|
|
341
|
+
version: string;
|
|
342
|
+
description?: string;
|
|
343
|
+
visibility: "area" | "company" | "global";
|
|
344
|
+
files: Array<{
|
|
345
|
+
path: string;
|
|
346
|
+
content: string;
|
|
347
|
+
}>;
|
|
348
|
+
}): Promise<SharedSkillSummary>;
|
|
349
|
+
archiveSharedSkill(skillId: string): Promise<SharedSkillSummary>;
|
|
336
350
|
getSchema(): Promise<any>;
|
|
337
351
|
private get;
|
|
338
352
|
private post;
|
|
@@ -399,3 +413,26 @@ export type BrainResolvedResource = {
|
|
|
399
413
|
resourceType: "source";
|
|
400
414
|
source: BrainSource;
|
|
401
415
|
};
|
|
416
|
+
export type SharedSkillSummary = {
|
|
417
|
+
id: string;
|
|
418
|
+
areaId: string;
|
|
419
|
+
areaName: string;
|
|
420
|
+
name: string;
|
|
421
|
+
version: string;
|
|
422
|
+
description?: string;
|
|
423
|
+
hash: string;
|
|
424
|
+
visibility: "area" | "company" | "global";
|
|
425
|
+
revision: number;
|
|
426
|
+
fileCount: number;
|
|
427
|
+
byteSize: number;
|
|
428
|
+
uri: string;
|
|
429
|
+
httpsUrl: string;
|
|
430
|
+
updatedAt: number;
|
|
431
|
+
};
|
|
432
|
+
export type SharedSkillBundle = Omit<SharedSkillSummary, "fileCount" | "byteSize"> & {
|
|
433
|
+
files: Array<{
|
|
434
|
+
path: string;
|
|
435
|
+
content: string;
|
|
436
|
+
sha256: string;
|
|
437
|
+
}>;
|
|
438
|
+
};
|
package/dist/client.js
CHANGED
|
@@ -512,6 +512,23 @@ export class DayOfWeekClient {
|
|
|
512
512
|
async listSkillBundles() {
|
|
513
513
|
return this.get("/skill?list=1");
|
|
514
514
|
}
|
|
515
|
+
// ── Shared skills ─────────────────────────────────────────────────────────
|
|
516
|
+
//
|
|
517
|
+
// Skills other users published into shared knowledge areas. Discovery is
|
|
518
|
+
// server-owned, like the named bundles above: the CLI ships no skill names,
|
|
519
|
+
// and the server decides which skills this device's user may see.
|
|
520
|
+
async listSharedSkills() {
|
|
521
|
+
return this.get("/brain/skills");
|
|
522
|
+
}
|
|
523
|
+
async getSharedSkill(skillId) {
|
|
524
|
+
return this.get(`/brain/skills/${encodeURIComponent(skillId)}`);
|
|
525
|
+
}
|
|
526
|
+
async publishSharedSkill(input) {
|
|
527
|
+
return this.post("/brain/skills", input);
|
|
528
|
+
}
|
|
529
|
+
async archiveSharedSkill(skillId) {
|
|
530
|
+
return this.delete(`/brain/skills/${encodeURIComponent(skillId)}`);
|
|
531
|
+
}
|
|
515
532
|
// ── Schema ────────────────────────────────────────────────────────────────
|
|
516
533
|
async getSchema() {
|
|
517
534
|
return this.get("/schema");
|
package/dist/skills.d.ts
CHANGED
|
@@ -8,8 +8,31 @@ export type SkillBundle = {
|
|
|
8
8
|
sha256?: string;
|
|
9
9
|
}>;
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Where an installed skill came from — recorded in `.dayofweek-skill.json` so
|
|
13
|
+
* `skill update` and `skill status --check` can go back to the same origin.
|
|
14
|
+
* "platform" = a named bundle from the built-in endpoint; "shared" = a skill
|
|
15
|
+
* another user published into a shared knowledge area.
|
|
16
|
+
*/
|
|
17
|
+
export type SkillOrigin = {
|
|
18
|
+
source: "platform";
|
|
19
|
+
} | {
|
|
20
|
+
source: "shared";
|
|
21
|
+
skillId: string;
|
|
22
|
+
areaId: string;
|
|
23
|
+
uri: string;
|
|
24
|
+
};
|
|
25
|
+
export type InstalledSkillMetadata = {
|
|
26
|
+
name: string;
|
|
27
|
+
version: string;
|
|
28
|
+
hash: string;
|
|
29
|
+
files: Record<string, string>;
|
|
30
|
+
origin?: SkillOrigin;
|
|
31
|
+
};
|
|
32
|
+
/** Parse `<dir>/.dayofweek-skill.json`; null when absent or malformed. */
|
|
33
|
+
export declare function readInstalledSkill(targetDir: string): InstalledSkillMetadata | null;
|
|
11
34
|
export declare function validateSkillBundle(bundle: SkillBundle): SkillBundle;
|
|
12
|
-
export declare function writeSkillBundle(input: SkillBundle, targetDir: string): {
|
|
35
|
+
export declare function writeSkillBundle(input: SkillBundle, targetDir: string, origin?: SkillOrigin): {
|
|
13
36
|
written: number;
|
|
14
37
|
unchanged: number;
|
|
15
38
|
conflicts: string[];
|
package/dist/skills.js
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
+
/** Parse `<dir>/.dayofweek-skill.json`; null when absent or malformed. */
|
|
5
|
+
export function readInstalledSkill(targetDir) {
|
|
6
|
+
const metadataPath = join(targetDir, ".dayofweek-skill.json");
|
|
7
|
+
if (!existsSync(metadataPath))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(readFileSync(metadataPath, "utf8"));
|
|
11
|
+
if (typeof parsed.name !== "string" || typeof parsed.version !== "string")
|
|
12
|
+
return null;
|
|
13
|
+
return parsed;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
4
19
|
function sha256(value) {
|
|
5
20
|
return createHash("sha256").update(value).digest("hex");
|
|
6
21
|
}
|
|
@@ -33,7 +48,7 @@ export function validateSkillBundle(bundle) {
|
|
|
33
48
|
throw new Error("Skill bundle manifest checksum mismatch");
|
|
34
49
|
return { ...bundle, hash: actualBundleHash, files };
|
|
35
50
|
}
|
|
36
|
-
export function writeSkillBundle(input, targetDir) {
|
|
51
|
+
export function writeSkillBundle(input, targetDir, origin) {
|
|
37
52
|
const bundle = validateSkillBundle(input);
|
|
38
53
|
let filesWritten = 0;
|
|
39
54
|
let unchanged = 0;
|
|
@@ -72,6 +87,6 @@ export function writeSkillBundle(input, targetDir) {
|
|
|
72
87
|
filesWritten++;
|
|
73
88
|
}
|
|
74
89
|
mkdirSync(targetDir, { recursive: true });
|
|
75
|
-
writeFileSync(metadataPath, JSON.stringify({ name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes }, null, 2), "utf8");
|
|
90
|
+
writeFileSync(metadataPath, JSON.stringify({ name: bundle.name, version: bundle.version, hash: bundle.hash, files: hashes, ...(origin ? { origin } : {}) }, null, 2), "utf8");
|
|
76
91
|
return { written: filesWritten, unchanged, conflicts };
|
|
77
92
|
}
|
package/dist/uri.d.ts
CHANGED
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 {
|