@daldindev/agentic-skills 0.1.0 → 0.2.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/CHANGELOG.md +11 -0
- package/README.md +25 -1
- package/package.json +1 -1
- package/src/cli.mjs +7 -5
- package/src/install.mjs +41 -26
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ The format is based on Keep a Changelog and this project follows Semantic Versio
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-09-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `agentic-skills sync`: installs when the target is missing and updates when it is already there, so one command covers both states. It is the command for a `postinstall` hook, a CI step, or a container build, where the run cannot stop to ask which state it is in. Local edits are preserved as with `update`; `--force` overwrites them, so exit code 2 cannot happen.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- The note about a missing manifest is printed only when files were actually skipped, and now appears for every command rather than only `update`.
|
|
18
|
+
- The error `init` raises on a non-empty target points at `sync` for unattended runs.
|
|
19
|
+
|
|
9
20
|
## [0.1.0] - 2026-09-03
|
|
10
21
|
|
|
11
22
|
### Added
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ npx agentic-skills init
|
|
|
31
31
|
Requirements:
|
|
32
32
|
|
|
33
33
|
- Node.js `>=20.11`
|
|
34
|
-
- Network access to `codeload.github.com`, where GitHub serves source archives, when you run `init` or `
|
|
34
|
+
- Network access to `codeload.github.com`, where GitHub serves source archives, when you run `init`, `update`, or `sync`
|
|
35
35
|
|
|
36
36
|
## Quick Start
|
|
37
37
|
|
|
@@ -55,6 +55,7 @@ Point your assistant at `.agents/ARCHITECTURE.md`. It lists every component with
|
|
|
55
55
|
| --- | --- |
|
|
56
56
|
| `agentic-skills init` | Download ag-kit and install the content. Refuses a non-empty target unless `--force` |
|
|
57
57
|
| `agentic-skills update` | Download ag-kit again and update installed files, keeping the ones you edited |
|
|
58
|
+
| `agentic-skills sync` | Install if missing, update if present. One command that works in both states, for unattended runs |
|
|
58
59
|
| `agentic-skills status` | Show what is installed, from which upstream commit, and which files were changed locally |
|
|
59
60
|
|
|
60
61
|
| Option | Purpose |
|
|
@@ -92,6 +93,28 @@ To stay on a known upstream state instead of `main`, pass a tag or commit. The s
|
|
|
92
93
|
npx @daldindev/agentic-skills update --ref v2026.8.31
|
|
93
94
|
```
|
|
94
95
|
|
|
96
|
+
### Unattended runs
|
|
97
|
+
|
|
98
|
+
`sync` reconciles the install against upstream whatever state it is in: it creates the directory when it is missing and updates it when it is already there. Nothing has to be known about the target beforehand, which is what makes it the command to put where no one is watching — a `postinstall` hook, a CI step, a container build:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"scripts": {
|
|
103
|
+
"postinstall": "agentic-skills sync"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Every `npm i` then brings the install to the current upstream, on a fresh clone and on a checkout that already has the content alike.
|
|
109
|
+
|
|
110
|
+
Your edits survive it: a file you changed is kept, listed, and the command exits `2`. Reach for `--force` only when the install is a derived tree your project regenerates and never edits by hand, `.agents/` in `.gitignore` for instance:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
agentic-skills sync --force
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Copying that second line into a project where `.agents/` is committed means losing an edited skill on every `npm i`, silently. And `--force` only removes exit `2`: a download that cannot reach GitHub still exits `1`.
|
|
117
|
+
|
|
95
118
|
## What gets installed
|
|
96
119
|
|
|
97
120
|
| Upstream path | Installed as |
|
|
@@ -127,6 +150,7 @@ In scope:
|
|
|
127
150
|
- Downloading the upstream roles, skills, and workflows and installing them anywhere
|
|
128
151
|
- Updating an install while preserving local edits
|
|
129
152
|
- Recording upstream provenance with every install
|
|
153
|
+
- Running unattended, from a postinstall hook or a CI step, with one command that works in either state
|
|
130
154
|
|
|
131
155
|
Out of scope:
|
|
132
156
|
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
6
6
|
import { PACKAGE_ROOT, readJson } from "./fs-utils.mjs";
|
|
7
|
-
import { DEFAULT_INSTALL_DIR, init, status, update } from "./install.mjs";
|
|
7
|
+
import { DEFAULT_INSTALL_DIR, init, status, sync, update } from "./install.mjs";
|
|
8
8
|
import { UPSTREAM, extractPort, loadArchive, materialize } from "./upstream.mjs";
|
|
9
9
|
|
|
10
10
|
const HELP = `agentic-skills - install the ag-kit agent roles, skills, and workflows into any project
|
|
@@ -12,6 +12,7 @@ const HELP = `agentic-skills - install the ag-kit agent roles, skills, and workf
|
|
|
12
12
|
Usage:
|
|
13
13
|
agentic-skills init [options] Download ag-kit and install the content into <path>/<dir>
|
|
14
14
|
agentic-skills update [options] Download ag-kit again and update installed files, preserving local edits
|
|
15
|
+
agentic-skills sync [options] Install if missing, update if present; safe to run unattended
|
|
15
16
|
agentic-skills status [options] Show what is installed and which files were changed locally
|
|
16
17
|
|
|
17
18
|
Options:
|
|
@@ -100,10 +101,10 @@ const printPlan = (result) => {
|
|
|
100
101
|
);
|
|
101
102
|
list("Skipped (modified locally; use --force to overwrite)", plan.skip, (item) => `${item.file}: ${item.reason}`);
|
|
102
103
|
list("Kept (removed upstream but modified locally)", plan.keep, (item) => `${item.file}: ${item.reason}`);
|
|
103
|
-
if (result.
|
|
104
|
+
if (result.hadManifest === false && plan.skip.length) {
|
|
104
105
|
console.log("\nNo manifest was found, so files that differ from upstream were skipped. Re-run with --force to overwrite them.");
|
|
105
106
|
}
|
|
106
|
-
if (result.
|
|
107
|
+
if (result.created && !result.dryRun) {
|
|
107
108
|
console.log(`\nPoint your assistant at ${path.join(result.installDir, "ARCHITECTURE.md")} to get the inventory.`);
|
|
108
109
|
}
|
|
109
110
|
};
|
|
@@ -133,7 +134,7 @@ try {
|
|
|
133
134
|
console.log(pkg.version);
|
|
134
135
|
} else if (values.help || !command || command === "help") {
|
|
135
136
|
process.stdout.write(HELP);
|
|
136
|
-
} else if (command === "init" || command === "update") {
|
|
137
|
+
} else if (command === "init" || command === "update" || command === "sync") {
|
|
137
138
|
const incoming = await fetchIncoming();
|
|
138
139
|
try {
|
|
139
140
|
const options = {
|
|
@@ -144,7 +145,8 @@ try {
|
|
|
144
145
|
incomingDir: incoming.dir,
|
|
145
146
|
upstream: incoming.upstream,
|
|
146
147
|
};
|
|
147
|
-
const
|
|
148
|
+
const run = { init, update, sync }[command];
|
|
149
|
+
const result = await run(options);
|
|
148
150
|
if (values.json) console.log(JSON.stringify(result, null, 2));
|
|
149
151
|
else printPlan(result);
|
|
150
152
|
if (result.plan.skip.length || result.plan.keep.length) process.exitCode = 2;
|
package/src/install.mjs
CHANGED
|
@@ -120,49 +120,64 @@ async function hasContent(dir) {
|
|
|
120
120
|
return entries.length > 0;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Plan against the incoming tree, apply it, and record the manifest.
|
|
125
|
+
* The three commands differ only in the preconditions they enforce first.
|
|
126
|
+
*/
|
|
127
|
+
async function reconcile(mode, options) {
|
|
124
128
|
const { installDir } = resolveTarget(options);
|
|
125
129
|
const { incomingDir, upstream = null } = options;
|
|
126
|
-
const force = Boolean(options.force);
|
|
127
|
-
|
|
128
|
-
if (!force && (await hasContent(installDir))) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`${installDir} already exists and is not empty. Run "agentic-skills update" to update it, ` +
|
|
131
|
-
"or pass --force to overwrite managed files in place.",
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
130
|
|
|
131
|
+
const created = !(await hasContent(installDir));
|
|
135
132
|
const manifest = await loadManifest(installDir);
|
|
136
|
-
const plan = await planUpdate({ installDir, incomingDir, manifest, force });
|
|
133
|
+
const plan = await planUpdate({ installDir, incomingDir, manifest, force: Boolean(options.force) });
|
|
137
134
|
if (!options.dryRun) {
|
|
138
135
|
await applyPlan({ installDir, incomingDir, plan });
|
|
139
136
|
await writeManifest(installDir, plan.incoming, { previous: manifest, upstream });
|
|
140
137
|
}
|
|
141
|
-
return {
|
|
138
|
+
return {
|
|
139
|
+
mode,
|
|
140
|
+
installDir,
|
|
141
|
+
plan,
|
|
142
|
+
upstream,
|
|
143
|
+
created,
|
|
144
|
+
hadManifest: Boolean(manifest),
|
|
145
|
+
dryRun: Boolean(options.dryRun),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function init(options = {}) {
|
|
150
|
+
const { installDir } = resolveTarget(options);
|
|
151
|
+
|
|
152
|
+
if (!options.force && (await hasContent(installDir))) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`${installDir} already exists and is not empty. Run "agentic-skills update" to update it, ` +
|
|
155
|
+
'"agentic-skills sync" if this runs unattended, or pass --force to overwrite managed files in place.',
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return reconcile("init", options);
|
|
142
160
|
}
|
|
143
161
|
|
|
144
162
|
export async function update(options = {}) {
|
|
145
163
|
const { installDir } = resolveTarget(options);
|
|
146
|
-
const { incomingDir, upstream = null } = options;
|
|
147
164
|
|
|
148
165
|
if (!(await exists(installDir))) {
|
|
149
166
|
throw new Error(`${installDir} does not exist. Run "agentic-skills init" first.`);
|
|
150
167
|
}
|
|
151
168
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
dryRun: Boolean(options.dryRun),
|
|
165
|
-
};
|
|
169
|
+
return reconcile("update", options);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reconcile whatever state the target is in: install it when missing, update it
|
|
174
|
+
* when it is already there. Neither precondition applies, so this is the command
|
|
175
|
+
* to run unattended - a postinstall, a CI step, a container build - where one
|
|
176
|
+
* line has to work on a fresh clone and on an existing tree alike. Local edits
|
|
177
|
+
* are preserved exactly as with update, and --force overwrites them.
|
|
178
|
+
*/
|
|
179
|
+
export async function sync(options = {}) {
|
|
180
|
+
return reconcile("sync", options);
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
/** Local report only; nothing is fetched. */
|