deweyou-cli 0.2.1
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/CHANGELOG.md +18 -0
- package/README.md +221 -0
- package/dist/cache-CBEKVQeD.mjs +231 -0
- package/dist/context-BYyhbv5D.mjs +207 -0
- package/dist/deweyou.mjs +124 -0
- package/dist/doctor-Do7WDnrg.mjs +295 -0
- package/dist/init-ClloZBVB.mjs +353 -0
- package/dist/prompts-DVRcV560.mjs +100 -0
- package/package.json +40 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.1 - 2026-05-17
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- publish cli as deweyou-cli package
|
|
8
|
+
## 0.2.0 - 2026-05-17
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- automate cli release changelog
|
|
13
|
+
- colocate deweyou cli package
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- generate cli registry from assets
|
|
18
|
+
All notable `deweyou-cli` package changes will be documented in this file.
|
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# deweyou-cli
|
|
2
|
+
|
|
3
|
+
`deweyou-cli` bootstraps Dewey's personal agent workflows into any local
|
|
4
|
+
repository. It manages the repo-level wiring for selected skills and rules while
|
|
5
|
+
keeping the source assets in the central `deweyou/agents` hub.
|
|
6
|
+
|
|
7
|
+
The v0 scope is intentionally small:
|
|
8
|
+
|
|
9
|
+
- cache skills and rules from a local `deweyou/agents` checkout
|
|
10
|
+
- initialize a repository with selected skills and rules
|
|
11
|
+
- render the active agent context for the current repository
|
|
12
|
+
- diagnose whether the current repository is wired correctly
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
From npm:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g deweyou-cli
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
During v0, point the CLI at your local `deweyou/agents` checkout before updating
|
|
23
|
+
the cache:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
export DEWEYOU_AGENTS_SOURCE=/path/to/deweyou/agents
|
|
27
|
+
deweyou-cli agent update
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
cd /path/to/your/repo
|
|
34
|
+
deweyou-cli agent init
|
|
35
|
+
deweyou-cli agent doctor
|
|
36
|
+
deweyou-cli agent context --format markdown
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For a non-interactive setup that selects everything:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
deweyou-cli agent init --all --mode link --yes
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Mental Model
|
|
46
|
+
|
|
47
|
+
`deweyou/agents` is the asset hub. It owns `skills/` and `rules/`.
|
|
48
|
+
|
|
49
|
+
`deweyou-cli` is the workflow manager. It scans the hub assets, generates a
|
|
50
|
+
cache registry under your home directory, then writes a small
|
|
51
|
+
`.agents/manifest.json` into each repository so the repository knows which
|
|
52
|
+
assets are active.
|
|
53
|
+
|
|
54
|
+
Each repository chooses its own asset set. A coding repo can select coding
|
|
55
|
+
skills and rules; a writing or design repo can select different ones.
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
### `deweyou-cli agent update`
|
|
60
|
+
|
|
61
|
+
Refreshes the local Dewey asset cache from `DEWEYOU_AGENTS_SOURCE`.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
DEWEYOU_AGENTS_SOURCE=/path/to/deweyou/agents deweyou-cli agent update
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This command writes the global cache at:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
~/.deweyou/agents/
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run this after changing or pulling updates in the asset hub.
|
|
74
|
+
|
|
75
|
+
### `deweyou-cli agent init`
|
|
76
|
+
|
|
77
|
+
Initializes the current repository with selected skills and rules.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
deweyou-cli agent init
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Without selection flags, this opens an interactive setup where you choose:
|
|
84
|
+
|
|
85
|
+
- install mode
|
|
86
|
+
- skills
|
|
87
|
+
- rules
|
|
88
|
+
|
|
89
|
+
Scripted examples:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
deweyou-cli agent init --all --mode link --yes
|
|
93
|
+
deweyou-cli agent init --skills code-knowledge,deweyou-design --rules code-style
|
|
94
|
+
deweyou-cli agent init --dry-run
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Flags:
|
|
98
|
+
|
|
99
|
+
| Flag | Meaning |
|
|
100
|
+
|------|---------|
|
|
101
|
+
| `--all` | Select every skill and rule from the cached registry. |
|
|
102
|
+
| `--skills a,b` | Select only the listed skill ids. Values are comma-separated. |
|
|
103
|
+
| `--rules a,b` | Select only the listed rule ids. Values are comma-separated. |
|
|
104
|
+
| `--mode link\|copy\|pointer` | Choose how the repository references selected assets. |
|
|
105
|
+
| `--yes` | Run without prompts. Requires `--all`, `--skills`, or `--rules`. |
|
|
106
|
+
| `--dry-run` | Print the planned files without writing them. |
|
|
107
|
+
| `--force` | Replace existing Dewey-managed asset destinations when needed. |
|
|
108
|
+
|
|
109
|
+
`--yes` does not guess a default asset set. It only confirms a scripted
|
|
110
|
+
selection you already provided.
|
|
111
|
+
|
|
112
|
+
### `deweyou-cli agent context`
|
|
113
|
+
|
|
114
|
+
Prints the active Dewey agent context for the current repository.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
deweyou-cli agent context --format markdown
|
|
118
|
+
deweyou-cli agent context --format json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Formats:
|
|
122
|
+
|
|
123
|
+
| Format | Meaning |
|
|
124
|
+
|--------|---------|
|
|
125
|
+
| `markdown` | Human-readable instructions and asset paths. This is the default. |
|
|
126
|
+
| `json` | Structured context for tooling or future integrations. |
|
|
127
|
+
|
|
128
|
+
The context output tells an agent which skills and rules are active, where their
|
|
129
|
+
files live, whether the hub commit changed, and whether any selected asset hash
|
|
130
|
+
changed in the local cache.
|
|
131
|
+
|
|
132
|
+
### `deweyou-cli agent doctor`
|
|
133
|
+
|
|
134
|
+
Checks whether the current repository and local cache are healthy.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
deweyou-cli agent doctor
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
It verifies:
|
|
141
|
+
|
|
142
|
+
- local cache registry exists and is valid
|
|
143
|
+
- repository `.agents/manifest.json` exists and is valid
|
|
144
|
+
- `AGENTS.md` exists
|
|
145
|
+
- selected skills and rules still exist in the registry
|
|
146
|
+
- selected asset hashes match the repository's initialized snapshot
|
|
147
|
+
- selected asset files are present
|
|
148
|
+
- symlinks are valid when using `link` mode
|
|
149
|
+
|
|
150
|
+
The command exits with a non-zero status when a check fails.
|
|
151
|
+
|
|
152
|
+
## Install Modes
|
|
153
|
+
|
|
154
|
+
| Mode | Repository Writes | Best For |
|
|
155
|
+
|------|-------------------|----------|
|
|
156
|
+
| `link` | Symlinks selected assets into `.agents/skills/` and `.agents/rules/`. | Daily local work where updates should be immediately visible after cache refresh. |
|
|
157
|
+
| `copy` | Copies selected assets into `.agents/skills/` and `.agents/rules/`. | Repositories that should keep a snapshot of the selected assets. |
|
|
158
|
+
| `pointer` | Writes only `.agents/manifest.json` and `AGENTS.md`; assets stay in the global cache. | Minimal repo footprint and tooling that can follow absolute cache paths. |
|
|
159
|
+
|
|
160
|
+
## Files Created
|
|
161
|
+
|
|
162
|
+
Depending on the selected mode, `deweyou-cli agent init` may create or update:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
AGENTS.md
|
|
166
|
+
.agents/manifest.json
|
|
167
|
+
.agents/skills/<skill>/SKILL.md
|
|
168
|
+
.agents/rules/<rule>.md
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`AGENTS.md` receives a managed Dewey section that points agents at the selected
|
|
172
|
+
workflow context. Existing content outside that managed section is preserved.
|
|
173
|
+
|
|
174
|
+
## Safety Notes
|
|
175
|
+
|
|
176
|
+
- Run `deweyou-cli agent update` before `deweyou-cli agent init`.
|
|
177
|
+
- Asset ids must be kebab-case and must exist in the cached registry.
|
|
178
|
+
- `--force` only replaces destinations that are already Dewey-managed. It
|
|
179
|
+
refuses to overwrite unrelated user-created files or directories.
|
|
180
|
+
- `--dry-run` is the safest way to preview what `init` would write.
|
|
181
|
+
- In v0, the asset source is local. Set `DEWEYOU_AGENTS_SOURCE` to the
|
|
182
|
+
`deweyou/agents` checkout you want to use.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
The CLI source and tests are written in TypeScript. Vite+ builds the published
|
|
187
|
+
JavaScript files into `dist/`.
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
npm run typecheck
|
|
191
|
+
npm test
|
|
192
|
+
npm run test:coverage
|
|
193
|
+
npm run build
|
|
194
|
+
npm pack --dry-run
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Release
|
|
198
|
+
|
|
199
|
+
Merging CLI package changes into `main` runs the release workflow. It typechecks,
|
|
200
|
+
runs tests, verifies the package with `npm pack --dry-run`, infers the next
|
|
201
|
+
version from conventional commit messages, prepends [CHANGELOG.md](./CHANGELOG.md),
|
|
202
|
+
tags `cli-vX.Y.Z`, and publishes `deweyou-cli` to npm.
|
|
203
|
+
|
|
204
|
+
Release commit rules:
|
|
205
|
+
|
|
206
|
+
- `feat:` creates a minor release.
|
|
207
|
+
- `fix:`, `perf:`, and `refactor:` create a patch release.
|
|
208
|
+
- `!` or `BREAKING CHANGE` creates a major release.
|
|
209
|
+
- `docs:` entries are included in the changelog only when another releasable CLI
|
|
210
|
+
commit is present.
|
|
211
|
+
- `test:` and `chore:` do not publish by themselves.
|
|
212
|
+
|
|
213
|
+
## Relationship To `deweyou/agents`
|
|
214
|
+
|
|
215
|
+
`deweyou/agents` continues to provide the actual skills, rules, and asset
|
|
216
|
+
validation workflow. The CLI generates the cache registry during
|
|
217
|
+
`deweyou-cli agent update`.
|
|
218
|
+
|
|
219
|
+
`deweyou-cli` does not replace those assets. It gives every repository a
|
|
220
|
+
repeatable way to choose and wire the assets it wants, without manually copying
|
|
221
|
+
or linking the same files again and again.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import yaml from "js-yaml";
|
|
8
|
+
//#region src/cli/registry.ts
|
|
9
|
+
const { load: loadYaml } = yaml;
|
|
10
|
+
async function loadRegistry(root) {
|
|
11
|
+
return { assets: {
|
|
12
|
+
skills: await scanSkills(root),
|
|
13
|
+
rules: await scanRules(root)
|
|
14
|
+
} };
|
|
15
|
+
}
|
|
16
|
+
async function scanSkills(root) {
|
|
17
|
+
const skillsDir = join(root, "skills");
|
|
18
|
+
const assets = {};
|
|
19
|
+
for (const entry of await safeReaddir(skillsDir)) {
|
|
20
|
+
if (!entry.isDirectory()) continue;
|
|
21
|
+
const skillPath = join(skillsDir, entry.name);
|
|
22
|
+
const frontmatter = await parseFrontmatter(join(skillPath, "SKILL.md"));
|
|
23
|
+
const name = frontmatter.name;
|
|
24
|
+
if (name !== entry.name) throw new Error(`skill ${entry.name} name must match frontmatter`);
|
|
25
|
+
assets[name] = {
|
|
26
|
+
path: toPosix(relative(root, skillPath)),
|
|
27
|
+
description: frontmatter.description,
|
|
28
|
+
hash: await hashDirectory(skillPath),
|
|
29
|
+
tags: frontmatter.tags
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return sortObject(assets);
|
|
33
|
+
}
|
|
34
|
+
async function scanRules(root) {
|
|
35
|
+
const rulesDir = join(root, "rules");
|
|
36
|
+
const assets = {};
|
|
37
|
+
for (const entry of await safeReaddir(rulesDir)) {
|
|
38
|
+
if (!entry.isFile()) continue;
|
|
39
|
+
if (entry.name === "README.md") continue;
|
|
40
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
41
|
+
const rulePath = join(rulesDir, entry.name);
|
|
42
|
+
const frontmatter = await parseFrontmatter(rulePath);
|
|
43
|
+
const expectedName = basename(entry.name, ".md");
|
|
44
|
+
const name = frontmatter.name;
|
|
45
|
+
if (name !== expectedName) throw new Error(`rule ${expectedName} name must match frontmatter`);
|
|
46
|
+
assets[name] = {
|
|
47
|
+
path: toPosix(relative(root, rulePath)),
|
|
48
|
+
description: frontmatter.description,
|
|
49
|
+
hash: await hashFile(rulePath),
|
|
50
|
+
tags: frontmatter.tags
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return sortObject(assets);
|
|
54
|
+
}
|
|
55
|
+
async function parseFrontmatter(path) {
|
|
56
|
+
const match = (await readFile(path, "utf8")).match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
57
|
+
if (!match) throw new Error(`${path} must include YAML frontmatter`);
|
|
58
|
+
const frontmatter = loadYaml(match[1]);
|
|
59
|
+
if (!isPlainObject(frontmatter)) throw new Error(`${path} frontmatter must be an object`);
|
|
60
|
+
return {
|
|
61
|
+
name: requiredString(frontmatter.name, `${path} frontmatter name`),
|
|
62
|
+
description: requiredString(frontmatter.description, `${path} frontmatter description`),
|
|
63
|
+
tags: optionalTags(frontmatter.tags, `${path} frontmatter tags`)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function requiredString(value, label) {
|
|
67
|
+
if (typeof value !== "string" || value.length === 0) throw new Error(`${label} must be a non-empty string`);
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function optionalTags(value, label) {
|
|
71
|
+
if (value === void 0) return [];
|
|
72
|
+
if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
|
|
73
|
+
return value.map((tag, index) => requiredString(tag, `${label}[${index}]`));
|
|
74
|
+
}
|
|
75
|
+
async function safeReaddir(path) {
|
|
76
|
+
try {
|
|
77
|
+
return await readdir(path, { withFileTypes: true });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
if (!(error instanceof Error) || !("code" in error)) throw error;
|
|
80
|
+
if (error.code === "ENOENT") return [];
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function hashDirectory(dir) {
|
|
85
|
+
const files = await collectFiles(dir);
|
|
86
|
+
const hash = createHash("sha256");
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
const relativePath = toPosix(relative(dir, file));
|
|
89
|
+
hash.update(relativePath);
|
|
90
|
+
hash.update("\0");
|
|
91
|
+
hash.update(await readFile(file));
|
|
92
|
+
hash.update("\0");
|
|
93
|
+
}
|
|
94
|
+
return `sha256:${hash.digest("hex")}`;
|
|
95
|
+
}
|
|
96
|
+
async function collectFiles(dir) {
|
|
97
|
+
const files = [];
|
|
98
|
+
for (const entry of await safeReaddir(dir)) {
|
|
99
|
+
const path = join(dir, entry.name);
|
|
100
|
+
if (entry.isDirectory()) files.push(...await collectFiles(path));
|
|
101
|
+
else if (entry.isFile()) files.push(path);
|
|
102
|
+
}
|
|
103
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
104
|
+
}
|
|
105
|
+
async function hashFile(path) {
|
|
106
|
+
return `sha256:${createHash("sha256").update(await readFile(path)).digest("hex")}`;
|
|
107
|
+
}
|
|
108
|
+
function sortObject(object) {
|
|
109
|
+
return Object.fromEntries(Object.entries(object).sort(([a], [b]) => a.localeCompare(b)));
|
|
110
|
+
}
|
|
111
|
+
function toPosix(path) {
|
|
112
|
+
return path.split("\\").join("/");
|
|
113
|
+
}
|
|
114
|
+
function isPlainObject(value) {
|
|
115
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/cli/manifest.ts
|
|
119
|
+
async function readJson(path, fallback) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (!(error instanceof Error) || !("code" in error)) throw error;
|
|
124
|
+
if (error.code === "ENOENT" && arguments.length > 1) return fallback;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function writeJson(path, value) {
|
|
129
|
+
await mkdir(dirname(path), { recursive: true });
|
|
130
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/cli/source.ts
|
|
134
|
+
const DEFAULT_SOURCE = "deweyou/agents";
|
|
135
|
+
function resolveSourceRoot({ env = process.env } = {}) {
|
|
136
|
+
if (env.DEWEYOU_AGENTS_SOURCE) return env.DEWEYOU_AGENTS_SOURCE;
|
|
137
|
+
throw new Error(`No local asset source configured. Set DEWEYOU_AGENTS_SOURCE to a checkout of ${DEFAULT_SOURCE}.`);
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/cli/cache.ts
|
|
141
|
+
const CLI_VERSION = "0.1.0";
|
|
142
|
+
const execFileAsync = promisify(execFile);
|
|
143
|
+
function cachePaths({ homeDir = homedir() } = {}) {
|
|
144
|
+
const root = join(homeDir, ".deweyou", "agents");
|
|
145
|
+
return {
|
|
146
|
+
root,
|
|
147
|
+
assetsRoot: join(root, "assets"),
|
|
148
|
+
manifestPath: join(root, "manifest.json")
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function updateCache({ homeDir = homedir(), sourceRoot, cliVersion = CLI_VERSION } = {}) {
|
|
152
|
+
if (!sourceRoot) throw new Error("sourceRoot is required to update the Dewey assets cache");
|
|
153
|
+
const registry = await loadRegistry(sourceRoot);
|
|
154
|
+
const source = await resolveSourceSnapshot(sourceRoot);
|
|
155
|
+
const paths = cachePaths({ homeDir });
|
|
156
|
+
const tempAssetsRoot = join(paths.root, `assets.tmp-${process.pid}-${Date.now()}`);
|
|
157
|
+
try {
|
|
158
|
+
await mkdir(paths.root, { recursive: true });
|
|
159
|
+
await rm(tempAssetsRoot, {
|
|
160
|
+
recursive: true,
|
|
161
|
+
force: true
|
|
162
|
+
});
|
|
163
|
+
await mkdir(tempAssetsRoot, { recursive: true });
|
|
164
|
+
await writeJson(join(tempAssetsRoot, "registry.json"), registry);
|
|
165
|
+
await copyAssetDirectory(sourceRoot, tempAssetsRoot, "skills");
|
|
166
|
+
await copyAssetDirectory(sourceRoot, tempAssetsRoot, "rules");
|
|
167
|
+
await rm(paths.assetsRoot, {
|
|
168
|
+
recursive: true,
|
|
169
|
+
force: true
|
|
170
|
+
});
|
|
171
|
+
await rename(tempAssetsRoot, paths.assetsRoot);
|
|
172
|
+
const manifest = {
|
|
173
|
+
source,
|
|
174
|
+
cliVersion,
|
|
175
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
176
|
+
};
|
|
177
|
+
await writeJson(paths.manifestPath, manifest);
|
|
178
|
+
return manifest;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
await rm(tempAssetsRoot, {
|
|
181
|
+
recursive: true,
|
|
182
|
+
force: true
|
|
183
|
+
});
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function runUpdate(flags = {}) {
|
|
188
|
+
const sourceRoot = flags.sourceRoot ?? resolveSourceRoot();
|
|
189
|
+
const manifest = await updateCache({
|
|
190
|
+
homeDir: flags.homeDir,
|
|
191
|
+
sourceRoot,
|
|
192
|
+
cliVersion: CLI_VERSION
|
|
193
|
+
});
|
|
194
|
+
const sourceLabel = manifest.source.commit ?? "local files";
|
|
195
|
+
console.log(`Updated Dewey agent assets from ${sourceLabel}`);
|
|
196
|
+
return manifest;
|
|
197
|
+
}
|
|
198
|
+
async function resolveSourceSnapshot(sourceRoot) {
|
|
199
|
+
return {
|
|
200
|
+
root: sourceRoot,
|
|
201
|
+
commit: await resolveGitCommit(sourceRoot)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function resolveGitCommit(sourceRoot) {
|
|
205
|
+
try {
|
|
206
|
+
const { stdout } = await execFileAsync("git", [
|
|
207
|
+
"-C",
|
|
208
|
+
sourceRoot,
|
|
209
|
+
"rev-parse",
|
|
210
|
+
"HEAD"
|
|
211
|
+
]);
|
|
212
|
+
return stdout.trim() || null;
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function copyAssetDirectory(sourceRoot, assetsRoot, name) {
|
|
218
|
+
const source = join(sourceRoot, name);
|
|
219
|
+
try {
|
|
220
|
+
await stat(source);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
/* v8 ignore next -- defensive guard for non-Node filesystem errors */
|
|
223
|
+
if (!(error instanceof Error) || !("code" in error)) throw error;
|
|
224
|
+
if (error.code === "ENOENT") return;
|
|
225
|
+
/* v8 ignore next -- stat() error variants are surfaced unchanged */
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
await cp(source, join(assetsRoot, name), { recursive: true });
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
export { cachePaths, writeJson as n, runUpdate, readJson as t };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { t as usageError } from "./deweyou.mjs";
|
|
2
|
+
import { cachePaths, t as readJson } from "./cache-CBEKVQeD.mjs";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
//#region src/cli/context.ts
|
|
7
|
+
const MISSING_REPO_MANIFEST = "This repository has not been initialized. Run `deweyou-cli agent init`.";
|
|
8
|
+
const MISSING_CACHE_REGISTRY = "Dewey asset cache is missing. Run `deweyou-cli agent update`.";
|
|
9
|
+
const VALID_FORMATS = new Set(["markdown", "json"]);
|
|
10
|
+
async function resolveContext({ repoRoot = process.cwd(), homeDir = homedir() } = {}) {
|
|
11
|
+
const manifest = await readJson(join(repoRoot, ".agents", "manifest.json"), null);
|
|
12
|
+
if (!manifest) return {
|
|
13
|
+
ok: false,
|
|
14
|
+
error: MISSING_REPO_MANIFEST
|
|
15
|
+
};
|
|
16
|
+
const paths = cachePaths({ homeDir });
|
|
17
|
+
const registry = await readRegistry(join(paths.assetsRoot, "registry.json"));
|
|
18
|
+
const cacheManifest = await readJson(paths.manifestPath, null);
|
|
19
|
+
if (!registry) return {
|
|
20
|
+
ok: false,
|
|
21
|
+
error: MISSING_CACHE_REGISTRY
|
|
22
|
+
};
|
|
23
|
+
const missing = findMissingAssets(manifest, registry);
|
|
24
|
+
if (missing.length > 0) return {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: `Repository manifest references assets missing from the Dewey registry: ${missing.join(", ")}`
|
|
27
|
+
};
|
|
28
|
+
const context = {
|
|
29
|
+
ok: true,
|
|
30
|
+
repo: {
|
|
31
|
+
root: repoRoot,
|
|
32
|
+
mode: manifest.mode
|
|
33
|
+
},
|
|
34
|
+
runtime: {
|
|
35
|
+
sourceCommit: cacheManifest?.source?.commit ?? null,
|
|
36
|
+
repoSourceCommit: manifest.source?.commit ?? null
|
|
37
|
+
},
|
|
38
|
+
assets: {
|
|
39
|
+
skills: selectedAssets({
|
|
40
|
+
names: manifest.assets?.skills ?? [],
|
|
41
|
+
registryAssets: registry.assets?.skills ?? {},
|
|
42
|
+
manifestAssets: manifest.assetSnapshot?.skills ?? {},
|
|
43
|
+
useManifestMetadata: manifest.mode === "copy",
|
|
44
|
+
pathForAsset: (name, asset) => skillPath({
|
|
45
|
+
repoRoot,
|
|
46
|
+
manifest,
|
|
47
|
+
name,
|
|
48
|
+
asset
|
|
49
|
+
})
|
|
50
|
+
}),
|
|
51
|
+
rules: selectedAssets({
|
|
52
|
+
names: manifest.assets?.rules ?? [],
|
|
53
|
+
registryAssets: registry.assets?.rules ?? {},
|
|
54
|
+
manifestAssets: manifest.assetSnapshot?.rules ?? {},
|
|
55
|
+
useManifestMetadata: manifest.mode === "copy",
|
|
56
|
+
pathForAsset: (name, asset) => rulePath({
|
|
57
|
+
repoRoot,
|
|
58
|
+
manifest,
|
|
59
|
+
name,
|
|
60
|
+
asset
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
_notice: {
|
|
65
|
+
update: sourceNotice(cacheManifest, manifest),
|
|
66
|
+
assets: selectedAssetNotice(manifest, registry)
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const missingPaths = await findMissingPaths(context);
|
|
70
|
+
if (missingPaths.length > 0) return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: `Selected Dewey asset paths are missing: ${missingPaths.join(", ")}`
|
|
73
|
+
};
|
|
74
|
+
return context;
|
|
75
|
+
}
|
|
76
|
+
function renderMarkdownContext(context) {
|
|
77
|
+
if (context.ok === false) return `# Dewey Agent Context
|
|
78
|
+
|
|
79
|
+
${context.error}
|
|
80
|
+
`;
|
|
81
|
+
return `# Dewey Agent Context
|
|
82
|
+
|
|
83
|
+
## Required Protocol
|
|
84
|
+
|
|
85
|
+
- Use the active skills and rules listed here for this repository.
|
|
86
|
+
- Read only the referenced asset files when their instructions are needed.
|
|
87
|
+
- Do not dump full skill or rule bodies into chat context.
|
|
88
|
+
|
|
89
|
+
## Active Skills
|
|
90
|
+
|
|
91
|
+
${renderAssets(context.assets.skills)}
|
|
92
|
+
|
|
93
|
+
## Active Rules
|
|
94
|
+
|
|
95
|
+
${renderAssets(context.assets.rules)}
|
|
96
|
+
|
|
97
|
+
## Runtime Notices
|
|
98
|
+
|
|
99
|
+
${renderNotices(context)}
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
async function runContext(flags = {}) {
|
|
103
|
+
const format = flags.format ?? "markdown";
|
|
104
|
+
if (!VALID_FORMATS.has(format)) throw usageError("format must be one of markdown or json");
|
|
105
|
+
const context = await resolveContext({
|
|
106
|
+
repoRoot: flags.repoRoot ?? process.cwd(),
|
|
107
|
+
homeDir: flags.homeDir ?? homedir()
|
|
108
|
+
});
|
|
109
|
+
if (format === "json") console.log(JSON.stringify(context, null, 2));
|
|
110
|
+
else console.log(renderMarkdownContext(context));
|
|
111
|
+
return context;
|
|
112
|
+
}
|
|
113
|
+
async function readRegistry(path) {
|
|
114
|
+
try {
|
|
115
|
+
const registry = JSON.parse(await readFile(path, "utf8"));
|
|
116
|
+
return {
|
|
117
|
+
...registry,
|
|
118
|
+
assets: {
|
|
119
|
+
skills: registry.assets?.skills ?? {},
|
|
120
|
+
rules: registry.assets?.rules ?? {}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
} catch (error) {
|
|
124
|
+
/* v8 ignore next -- defensive guard for non-Node filesystem errors */
|
|
125
|
+
if (!(error instanceof Error) || !("code" in error)) throw error;
|
|
126
|
+
if (error.code === "ENOENT") return null;
|
|
127
|
+
/* v8 ignore next -- non-missing read errors should bubble to the caller */
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function findMissingAssets(manifest, registry) {
|
|
132
|
+
return [...(manifest.assets?.skills ?? []).filter((name) => !registry.assets.skills[name]).map((name) => `skill:${name}`), ...(manifest.assets?.rules ?? []).filter((name) => !registry.assets.rules[name]).map((name) => `rule:${name}`)];
|
|
133
|
+
}
|
|
134
|
+
function selectedAssets({ names, registryAssets, manifestAssets, useManifestMetadata, pathForAsset }) {
|
|
135
|
+
return names.map((name) => {
|
|
136
|
+
const asset = registryAssets[name];
|
|
137
|
+
const metadata = useManifestMetadata && manifestAssets[name] ? manifestAssets[name] : asset;
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
description: metadata.description,
|
|
141
|
+
hash: metadata.hash,
|
|
142
|
+
path: pathForAsset(name, asset)
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function skillPath({ repoRoot, manifest, name, asset }) {
|
|
147
|
+
if (manifest.mode === "pointer") return join(manifest.cacheRoot, asset.path, "SKILL.md");
|
|
148
|
+
return join(repoRoot, ".agents", "skills", name, "SKILL.md");
|
|
149
|
+
}
|
|
150
|
+
function rulePath({ repoRoot, manifest, name, asset }) {
|
|
151
|
+
if (manifest.mode === "pointer") return join(manifest.cacheRoot, asset.path);
|
|
152
|
+
return join(repoRoot, ".agents", "rules", `${name}.md`);
|
|
153
|
+
}
|
|
154
|
+
function renderAssets(assets) {
|
|
155
|
+
if (assets.length === 0) return "- None selected.";
|
|
156
|
+
return assets.map((asset) => `- ${asset.name} - ${asset.description}\n Hash: ${asset.hash}\n Path: ${asset.path}`).join("\n");
|
|
157
|
+
}
|
|
158
|
+
function sourceNotice(cacheManifest, manifest) {
|
|
159
|
+
const current = cacheManifest?.source?.commit ?? null;
|
|
160
|
+
const initialized = manifest.source?.commit ?? null;
|
|
161
|
+
if (!current || !initialized || current === initialized) return null;
|
|
162
|
+
return `Dewey asset cache is at commit ${current}; this repo was initialized with ${initialized}. Run \`deweyou-cli agent init\` to refresh selected assets.`;
|
|
163
|
+
}
|
|
164
|
+
function selectedAssetNotice(manifest, registry) {
|
|
165
|
+
const changed = [...changedAssets({
|
|
166
|
+
prefix: "skill",
|
|
167
|
+
names: manifest.assets?.skills ?? [],
|
|
168
|
+
registryAssets: registry.assets?.skills ?? {},
|
|
169
|
+
manifestAssets: manifest.assetSnapshot?.skills ?? {}
|
|
170
|
+
}), ...changedAssets({
|
|
171
|
+
prefix: "rule",
|
|
172
|
+
names: manifest.assets?.rules ?? [],
|
|
173
|
+
registryAssets: registry.assets?.rules ?? {},
|
|
174
|
+
manifestAssets: manifest.assetSnapshot?.rules ?? {}
|
|
175
|
+
})];
|
|
176
|
+
if (changed.length === 0) return null;
|
|
177
|
+
return `Selected Dewey assets changed in cache: ${changed.join(", ")}. Run \`deweyou-cli agent init\` to refresh.`;
|
|
178
|
+
}
|
|
179
|
+
function changedAssets({ prefix, names, registryAssets, manifestAssets }) {
|
|
180
|
+
return names.filter((name) => {
|
|
181
|
+
const current = registryAssets[name]?.hash;
|
|
182
|
+
const initialized = manifestAssets[name]?.hash;
|
|
183
|
+
return Boolean(current && initialized && current !== initialized);
|
|
184
|
+
}).map((name) => `${prefix}:${name}`);
|
|
185
|
+
}
|
|
186
|
+
function renderNotices(context) {
|
|
187
|
+
const notices = [context._notice.update, context._notice.assets].filter(Boolean);
|
|
188
|
+
if (notices.length === 0) return "- None.";
|
|
189
|
+
return notices.map((notice) => `- ${notice}`).join("\n");
|
|
190
|
+
}
|
|
191
|
+
async function findMissingPaths(context) {
|
|
192
|
+
const paths = [...context.assets.skills.map((asset) => asset.path), ...context.assets.rules.map((asset) => asset.path)];
|
|
193
|
+
return (await Promise.all(paths.map(async (path) => {
|
|
194
|
+
try {
|
|
195
|
+
await stat(path);
|
|
196
|
+
return null;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
/* v8 ignore next -- defensive guard for non-Node filesystem errors */
|
|
199
|
+
if (!(error instanceof Error) || !("code" in error)) throw error;
|
|
200
|
+
if (error.code === "ENOENT") return path;
|
|
201
|
+
/* v8 ignore next -- non-missing stat errors should bubble to the caller */
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}))).filter((path) => Boolean(path));
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { runContext };
|