@wipcomputer/wip-ldm-os 0.4.85-alpha.8 → 0.4.85-alpha.9

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/lib/deploy.mjs CHANGED
@@ -1258,6 +1258,58 @@ function copySkillTree(skillDir, dest, copyFullFolder = false) {
1258
1258
  }
1259
1259
  }
1260
1260
 
1261
+ const SKILL_COMPANION_DIRS = ['references', 'agents', 'scripts', 'assets'];
1262
+
1263
+ function listSkillCopyEntries(skillDir, copyFullFolder = false) {
1264
+ if (copyFullFolder) {
1265
+ try {
1266
+ return readdirSync(skillDir)
1267
+ .filter((entry) => entry !== '.DS_Store')
1268
+ .sort();
1269
+ } catch {
1270
+ return ['SKILL.md'];
1271
+ }
1272
+ }
1273
+
1274
+ const entries = ['SKILL.md'];
1275
+ for (const child of SKILL_COMPANION_DIRS) {
1276
+ if (existsSync(join(skillDir, child))) entries.push(`${child}/`);
1277
+ }
1278
+ return entries;
1279
+ }
1280
+
1281
+ function printSkillDryRunPlan({ sourceSkillDir, refsSrc, toolName, harnesses, workspace, entries }) {
1282
+ const formatTargetEntries = (base) => entries.map((entry) => join(base, entry));
1283
+ const harnessTargets = Object.entries(harnesses)
1284
+ .filter(([, h]) => h.detected && h.skills)
1285
+ .map(([name, h]) => ({ name, base: join(h.skills, toolName) }));
1286
+
1287
+ ok(`Skill: ${toolName}`);
1288
+ log(`Source: ${sourceSkillDir}`);
1289
+ log('Would copy:');
1290
+ for (const entry of entries) log(`- ${entry}`);
1291
+
1292
+ const permanentBase = join(LDM_EXTENSIONS, toolName);
1293
+ log('Permanent copy:');
1294
+ for (const target of formatTargetEntries(permanentBase)) log(`- ${target}`);
1295
+
1296
+ if (harnessTargets.length > 0) {
1297
+ log('Agent skill targets:');
1298
+ for (const target of harnessTargets) {
1299
+ log(`- ${target.name}: ${target.base}`);
1300
+ for (const entryTarget of formatTargetEntries(target.base)) log(` - ${entryTarget}`);
1301
+ }
1302
+ } else {
1303
+ log('Agent skill targets: no detected skill harnesses');
1304
+ }
1305
+
1306
+ if (existsSync(refsSrc) && workspace && existsSync(workspace)) {
1307
+ const homeRefsDest = join(workspace, 'settings', 'docs', 'skills', toolName);
1308
+ log('Workspace docs target:');
1309
+ log(`- ${homeRefsDest} (references/ only)`);
1310
+ }
1311
+ }
1312
+
1261
1313
  function installSkillFolder(skillDir, toolName, opts = {}) {
1262
1314
  const { harnesses, workspace } = getHarnesses();
1263
1315
 
@@ -1281,17 +1333,8 @@ function installSkillFolder(skillDir, toolName, opts = {}) {
1281
1333
  if (!existsSync(refsSrc) && existsSync(permanentRefs)) refsSrc = permanentRefs;
1282
1334
 
1283
1335
  if (DRY_RUN) {
1284
- const targets = Object.entries(harnesses)
1285
- .filter(([,h]) => h.detected && h.skills)
1286
- .map(([,h]) => join(h.skills, toolName));
1287
- ok(`Skill: ${toolName}`);
1288
- log(`Source: ${sourceSkillDir}`);
1289
- if (targets.length > 0) {
1290
- log(`Targets:`);
1291
- for (const target of targets) log(`- ${target}`);
1292
- } else {
1293
- log(`Targets: no detected skill harnesses`);
1294
- }
1336
+ const entries = listSkillCopyEntries(sourceSkillDir, opts.copyFullFolder);
1337
+ printSkillDryRunPlan({ sourceSkillDir, refsSrc, toolName, harnesses, workspace, entries });
1295
1338
  return true;
1296
1339
  }
1297
1340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.85-alpha.8",
3
+ "version": "0.4.85-alpha.9",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -24,6 +24,7 @@
24
24
  "test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
25
25
  "test:installer-hook-toolname": "node scripts/test-installer-hook-toolname.mjs",
26
26
  "test:installer-skill-directory": "node scripts/test-installer-skill-directory.mjs",
27
+ "test:installer-skill-dry-run-destinations": "node scripts/test-installer-skill-dry-run-destinations.mjs",
27
28
  "test:ldm-install-bin-shim": "node scripts/test-ldm-install-preserves-foreign-bin.mjs",
28
29
  "test:doctor-cron-target": "node scripts/test-doctor-cron-target.mjs",
29
30
  "test:bin-manifest": "node scripts/test-bin-manifest.mjs",
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+
6
+ const home = mkdtempSync(join(tmpdir(), 'ldm-skill-dry-run-home-'));
7
+ const source = mkdtempSync(join(tmpdir(), 'ldm-skill-dry-run-source-'));
8
+
9
+ function assert(condition, message) {
10
+ if (!condition) throw new Error(message);
11
+ }
12
+
13
+ try {
14
+ process.env.HOME = home;
15
+
16
+ for (const dir of ['.claude', '.openclaw', '.codex', '.agents']) {
17
+ mkdirSync(join(home, dir), { recursive: true });
18
+ }
19
+
20
+ const workspace = join(home, 'workspace');
21
+ mkdirSync(workspace, { recursive: true });
22
+ mkdirSync(join(home, '.ldm'), { recursive: true });
23
+ writeFileSync(join(home, '.ldm', 'config.json'), JSON.stringify({
24
+ workspace,
25
+ harnesses: {
26
+ 'claude-code': {
27
+ detected: true,
28
+ home: join(home, '.claude'),
29
+ skills: join(home, '.claude', 'skills'),
30
+ },
31
+ openclaw: {
32
+ detected: true,
33
+ home: join(home, '.openclaw'),
34
+ skills: join(home, '.openclaw', 'skills'),
35
+ },
36
+ codex: {
37
+ detected: true,
38
+ home: join(home, '.codex'),
39
+ skills: join(home, '.codex', 'skills'),
40
+ },
41
+ 'wip-agents': {
42
+ detected: true,
43
+ home: join(home, '.agents'),
44
+ skills: join(home, '.agents', 'skills'),
45
+ },
46
+ },
47
+ }, null, 2));
48
+
49
+ mkdirSync(join(source, 'references'), { recursive: true });
50
+ mkdirSync(join(source, 'agents'), { recursive: true });
51
+ writeFileSync(join(source, 'package.json'), JSON.stringify({
52
+ name: '@wipcomputer/wip-ai-chat-ui',
53
+ version: '0.1.1',
54
+ }, null, 2));
55
+ writeFileSync(join(source, 'SKILL.md'), '---\nname: wip-ai-chat-ui\ndescription: "test skill"\n---\n\n# Test Skill\n');
56
+ writeFileSync(join(source, 'references', 'stack.md'), '# Stack\n');
57
+ writeFileSync(join(source, 'agents', 'openai.yaml'), 'display_name: "WIP AI Chat UI"\n');
58
+
59
+ const { setFlags, installFromPath } = await import('../lib/deploy.mjs');
60
+
61
+ const lines = [];
62
+ const originalLog = console.log;
63
+ console.log = (...args) => lines.push(args.join(' '));
64
+
65
+ try {
66
+ setFlags({ dryRun: true, jsonOutput: false });
67
+ const result = await installFromPath(source);
68
+ assert(result.interfaces === 1, 'dry run should process one skill interface');
69
+ } finally {
70
+ console.log = originalLog;
71
+ }
72
+
73
+ const output = lines.join('\n');
74
+
75
+ for (const expected of [
76
+ 'Would copy:',
77
+ '- SKILL.md',
78
+ '- references/',
79
+ '- agents/',
80
+ 'Permanent copy:',
81
+ join(home, '.ldm', 'extensions', 'wip-ai-chat-ui', 'SKILL.md'),
82
+ join(home, '.ldm', 'extensions', 'wip-ai-chat-ui', 'references/'),
83
+ 'Agent skill targets:',
84
+ `claude-code: ${join(home, '.claude', 'skills', 'wip-ai-chat-ui')}`,
85
+ join(home, '.claude', 'skills', 'wip-ai-chat-ui', 'SKILL.md'),
86
+ `openclaw: ${join(home, '.openclaw', 'skills', 'wip-ai-chat-ui')}`,
87
+ join(home, '.openclaw', 'skills', 'wip-ai-chat-ui', 'references/'),
88
+ `codex: ${join(home, '.codex', 'skills', 'wip-ai-chat-ui')}`,
89
+ `wip-agents: ${join(home, '.agents', 'skills', 'wip-ai-chat-ui')}`,
90
+ 'Workspace docs target:',
91
+ `${join(workspace, 'settings', 'docs', 'skills', 'wip-ai-chat-ui')} (references/ only)`,
92
+ ]) {
93
+ assert(output.includes(expected), `dry-run output should include ${expected}\n\n${output}`);
94
+ }
95
+
96
+ console.log('installer skill dry-run destinations regression passed');
97
+ } finally {
98
+ rmSync(home, { recursive: true, force: true });
99
+ rmSync(source, { recursive: true, force: true });
100
+ }