cadet-agent 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,27 @@ Cadet-Agent is a cross-IDE agent framework for game-development workflows, with
11
11
  - `.continue/` contains Continue-specific authored files.
12
12
  - `.claude/` contains Claude Code-specific authored files.
13
13
  - `package-agent.ps1` builds the distributable `cadet-agent.zip` package.
14
+ - `publish-npm.ps1` publishes the CLI to npm using a token from `~/.npm_token`.
15
+
16
+ ## Quick Install
17
+
18
+ ```bash
19
+ npx cadet-agent@latest init
20
+ ```
21
+
22
+ This downloads the latest framework release and extracts it into your current directory. For a specific target directory:
23
+
24
+ ```bash
25
+ npx cadet-agent@latest init --target ./my-unity-project
26
+ ```
27
+
28
+ ## Manual Install
29
+
30
+ Download `cadet-agent.zip` from [GitHub Releases](https://github.com/naishtech/cadet-agent/releases) and extract it into your Unity project root:
31
+
32
+ ```powershell
33
+ Expand-Archive .\cadet-agent.zip -DestinationPath . -Force
34
+ ```
14
35
 
15
36
  ## Getting Started
16
37
  - For repository setup and package contents, see [.cadet/agent/README.md](.cadet/agent/README.md).
@@ -23,7 +44,7 @@ Cadet-Agent is a cross-IDE agent framework for game-development workflows, with
23
44
  ## Examples
24
45
 
25
46
  ### GitHub Copilot kickoff
26
- After extracting `cadet-agent.zip` into a game repository, open the repo in VS Code and start a kickoff chat using the Cadet prompt.
47
+ Run `npx cadet-agent@latest init` in your Unity project root, then open the repo in VS Code and start a kickoff chat using the Cadet prompt.
27
48
 
28
49
  ```text
29
50
  /cadet Help me bootstrap a beginner-friendly 2D racing prototype in Unity with AI opponents and a career progression loop.
@@ -59,7 +80,6 @@ If a specific game repository needs local conventions, add a policy file under `
59
80
  Running `./package-agent.ps1` produces `cadet-agent.zip` with this layout:
60
81
  - `.cadet/agent/core/`
61
82
  - `AGENTS.md`
62
- - `.github/cadet-copilot-instructions.md`
63
83
  - `.github/prompts/cadet.prompt.md`
64
84
  - `.cursor/rules/cadet-agent.md`
65
85
  - `.continue/rules/cadet-agent.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cadet-agent",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Cross-IDE agent framework for Unity/C# game-development — one-command install",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { dirname, join } from 'node:path';
4
- import { install } from './install.mjs';
4
+ import { install, sync } from './install.mjs';
5
5
 
6
6
  const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = dirname(__filename);
@@ -25,9 +25,12 @@ function showHelp() {
25
25
  Usage:
26
26
  npx cadet-agent@latest init Install Cadet-Agent into the current directory
27
27
  npx cadet-agent@latest init --target <dir> Install into a specific directory
28
+ npx cadet-agent@latest sync Update framework, preserving local policies/plans
29
+ npx cadet-agent@latest sync --target <dir> Sync a specific directory
28
30
 
29
31
  Options:
30
32
  --target, -t Target directory (default: current working directory)
33
+ --source Release API URL override (for forked deployments)
31
34
  --help, -h Show this help
32
35
  --version, -v Show version number
33
36
  `);
@@ -38,17 +41,25 @@ export async function run(argv) {
38
41
 
39
42
  // Parse --target <dir> or -t <dir>
40
43
  let targetDir = process.cwd();
44
+ let sourceUrl = null;
41
45
  const targetIdx = argv.indexOf('--target');
42
46
  const tIdx = argv.indexOf('-t');
47
+ const sourceIdx = argv.indexOf('--source');
43
48
  if (targetIdx !== -1 && argv[targetIdx + 1]) {
44
49
  targetDir = argv[targetIdx + 1];
45
50
  } else if (tIdx !== -1 && argv[tIdx + 1]) {
46
51
  targetDir = argv[tIdx + 1];
47
52
  }
53
+ if (sourceIdx !== -1 && argv[sourceIdx + 1]) {
54
+ sourceUrl = argv[sourceIdx + 1];
55
+ }
48
56
 
49
57
  switch (command) {
50
58
  case 'init':
51
- await install(targetDir);
59
+ await install(targetDir, { sourceUrl });
60
+ break;
61
+ case 'sync':
62
+ await sync(targetDir, { sourceUrl });
52
63
  break;
53
64
  case '--version':
54
65
  case '-v':
package/src/install.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { createWriteStream, mkdirSync, existsSync } from 'node:fs';
1
+ import { createWriteStream, mkdirSync, existsSync, readFileSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { pipeline } from 'node:stream/promises';
4
4
  import { createGunzip } from 'node:zlib';
@@ -8,9 +8,13 @@ import { Readable } from 'node:stream';
8
8
 
9
9
  // ── Constants ────────────────────────────────────────────────────────────────
10
10
 
11
- const GITHUB_API = 'https://api.github.com/repos/naishtech/cadet-agent/releases/latest';
11
+ const DEFAULT_API = 'https://api.github.com/repos/naishtech/cadet-agent/releases/latest';
12
12
  const USER_AGENT = 'cadet-agent-cli';
13
13
 
14
+ function resolveApiUrl(override) {
15
+ return override || process.env.CADET_AGENT_RELEASE_URL || DEFAULT_API;
16
+ }
17
+
14
18
  // ── ZIP parser (zero-dependency, handles store + deflate) ───────────────────
15
19
 
16
20
  const SIG_EOCD = 0x06054b50;
@@ -70,7 +74,12 @@ function extractFile(buf, entry, targetDir) {
70
74
  // Deflate
71
75
  data = inflateRawSync(compressed);
72
76
  } else {
73
- throw new Error(`Unsupported compression method ${method} for ${filename}`);
77
+ throw new Error(
78
+ `Unsupported compression method ${method} for ${filename}.\n` +
79
+ `This zip uses a compression format this CLI doesn't support.\n` +
80
+ `Try downloading cadet-agent.zip manually from:\n` +
81
+ ` https://github.com/naishtech/cadet-agent/releases/latest`
82
+ );
74
83
  }
75
84
 
76
85
  const outPath = join(targetDir, filename);
@@ -86,6 +95,15 @@ function extractZip(buf, targetDir) {
86
95
  const cdSize = read32(buf, eocdOff + 12);
87
96
  const cdOff = read32(buf, eocdOff + 16);
88
97
 
98
+ // ZIP64 detection — 0xFFFFFFFF in 32-bit fields means real values are in ZIP64 extra records
99
+ if (cdOff === 0xFFFFFFFF || cdSize === 0xFFFFFFFF) {
100
+ throw new Error(
101
+ 'This zip uses ZIP64 format, which is not supported.\n' +
102
+ 'Try downloading cadet-agent.zip manually from:\n' +
103
+ ' https://github.com/naishtech/cadet-agent/releases/latest'
104
+ );
105
+ }
106
+
89
107
  const paths = [];
90
108
  for (const entry of centralDirectoryEntries(buf, cdOff, cdSize)) {
91
109
  const outPath = extractFile(buf, entry, targetDir);
@@ -96,10 +114,11 @@ function extractZip(buf, targetDir) {
96
114
 
97
115
  // ── GitHub release download ─────────────────────────────────────────────────
98
116
 
99
- async function fetchLatestRelease() {
117
+ async function fetchLatestRelease(apiUrl) {
118
+ const url = resolveApiUrl(apiUrl);
100
119
  console.log('🔍 Fetching latest Cadet-Agent release...');
101
120
 
102
- const res = await fetch(GITHUB_API, {
121
+ const res = await fetch(url, {
103
122
  headers: {
104
123
  'User-Agent': USER_AGENT,
105
124
  'Accept': 'application/vnd.github+json',
@@ -169,11 +188,11 @@ async function downloadZip(url) {
169
188
 
170
189
  // ── Public install entry ────────────────────────────────────────────────────
171
190
 
172
- export async function install(targetDir) {
191
+ export async function install(targetDir, opts = {}) {
173
192
  console.log(`📦 Cadet-Agent — installing to ${targetDir}\n`);
174
193
 
175
194
  // 1. Fetch release metadata
176
- const release = await fetchLatestRelease();
195
+ const release = await fetchLatestRelease(opts.sourceUrl);
177
196
  console.log(` Latest: ${release.tag_name} (published ${release.published_at})\n`);
178
197
 
179
198
  // 2. Find zip asset
@@ -193,8 +212,7 @@ export async function install(targetDir) {
193
212
  // Print per-IDE next steps
194
213
  console.log('── Next steps ──');
195
214
  console.log(' GitHub Copilot:');
196
- console.log(' Rename .github\\cadet-copilot-instructions.md .github\\copilot-instructions.md');
197
- console.log(' Then start a chat: /cadet');
215
+ console.log(' Start a chat: /cadet');
198
216
  console.log(' Cursor:');
199
217
  console.log(' Already active — .cursor\\rules\\cadet-agent.md loads automatically');
200
218
  console.log(' Continue:');
@@ -203,3 +221,117 @@ export async function install(targetDir) {
203
221
  console.log(' Already active — .claude\\skills\\cadet-agent.md loads as a project skill');
204
222
  console.log('');
205
223
  }
224
+
225
+ // ── Manifest-aware extraction ───────────────────────────────────────────────
226
+
227
+ function matchesPreservedPath(filename, preservedPaths) {
228
+ // Normalize: strip leading dot (zip paths like ".cadet/agent/policies/...")
229
+ const normalized = filename.replace(/^\.?\/?/, '');
230
+ for (const preserved of preservedPaths) {
231
+ const p = preserved.replace(/^\.?\/?/, '');
232
+ if (normalized === p || normalized.startsWith(p + '/') || normalized.startsWith(p + '\\')) {
233
+ return true;
234
+ }
235
+ }
236
+ return false;
237
+ }
238
+
239
+ function extractZipWithManifest(buf, targetDir, { preserved, managed }) {
240
+ const eocdOff = findEocd(buf);
241
+ const cdSize = read32(buf, eocdOff + 12);
242
+ const cdOff = read32(buf, eocdOff + 16);
243
+
244
+ if (cdOff === 0xFFFFFFFF || cdSize === 0xFFFFFFFF) {
245
+ throw new Error(
246
+ 'This zip uses ZIP64 format, which is not supported.\n' +
247
+ 'Try downloading cadet-agent.zip manually from:\n' +
248
+ ' https://github.com/naishtech/cadet-agent/releases/latest'
249
+ );
250
+ }
251
+
252
+ const updated = [];
253
+ const preserved_list = [];
254
+ const added = [];
255
+
256
+ for (const entry of centralDirectoryEntries(buf, cdOff, cdSize)) {
257
+ if (matchesPreservedPath(entry.filename, preserved)) {
258
+ preserved_list.push(entry.filename);
259
+ continue;
260
+ }
261
+ const outPath = extractFile(buf, entry, targetDir);
262
+ // Check if this file already existed (updated vs added)
263
+ // We can't easily tell without checking before extraction, so approximate:
264
+ // If it's under a managed path, call it updated; otherwise added
265
+ const isManaged = managed.some(m => {
266
+ const mn = m.replace(/^\.?\/?/, '');
267
+ const fn = entry.filename.replace(/^\.?\/?/, '');
268
+ return fn === mn || fn.startsWith(mn + '/') || fn.startsWith(mn + '\\');
269
+ });
270
+ if (isManaged) {
271
+ updated.push(outPath);
272
+ } else {
273
+ added.push(outPath);
274
+ }
275
+ }
276
+
277
+ return { updated, preserved: preserved_list, added };
278
+ }
279
+
280
+ // ── Public sync entry ───────────────────────────────────────────────────────
281
+
282
+ export async function sync(targetDir, opts = {}) {
283
+ console.log(`🔄 Cadet-Agent — syncing ${targetDir}\n`);
284
+
285
+ // 1. Read existing manifest
286
+ const manifestPath = join(targetDir, '.cadet', 'agent', 'core', 'FrameworkManifest.json');
287
+ let existingManifest = null;
288
+ let oldVersion = 'none';
289
+ try {
290
+ existingManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
291
+ oldVersion = existingManifest.frameworkVersion || 'unknown';
292
+ console.log(` Existing install: v${oldVersion}`);
293
+ } catch {
294
+ console.log(' No existing install found — performing full install.\n');
295
+ return install(targetDir, opts);
296
+ }
297
+
298
+ // 2. Fetch release metadata
299
+ const release = await fetchLatestRelease(opts.sourceUrl);
300
+ const newVersion = release.tag_name;
301
+ console.log(` Latest: ${newVersion} (published ${release.published_at})\n`);
302
+
303
+ if (oldVersion === newVersion) {
304
+ console.log(`✅ Already up to date (v${oldVersion}). Nothing to sync.\n`);
305
+ return;
306
+ }
307
+
308
+ // 3. Download
309
+ const asset = findZipAsset(release);
310
+ const zipBuf = await downloadZip(asset.browser_download_url);
311
+ console.log(` Downloaded ${(zipBuf.length / 1024).toFixed(0)} KB\n`);
312
+
313
+ // 4. Extract with manifest awareness
314
+ console.log('📂 Extracting (preserving local policies and plans)...');
315
+ const result = extractZipWithManifest(zipBuf, targetDir, {
316
+ preserved: existingManifest.preservedPaths || [],
317
+ managed: existingManifest.managedPaths || [],
318
+ });
319
+
320
+ // 5. Report
321
+ console.log('');
322
+ console.log(`✅ Cadet-Agent synced: v${oldVersion} → ${newVersion}`);
323
+ console.log(` Updated: ${result.updated.length} files`);
324
+ if (result.preserved.length > 0) {
325
+ console.log(` Preserved: ${result.preserved.length} files (local policies/plans)`);
326
+ }
327
+ if (result.added.length > 0) {
328
+ console.log(` New: ${result.added.length} files`);
329
+ }
330
+ console.log('');
331
+
332
+ // Print per-IDE next steps
333
+ console.log('── Next steps ──');
334
+ console.log(' Framework files updated. Start a fresh chat for changes to take effect:');
335
+ console.log(' /cadet');
336
+ console.log('');
337
+ }