@keighleykodric/weeve 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/bin/weeve.js +169 -87
  2. package/package.json +1 -1
package/bin/weeve.js CHANGED
@@ -64,6 +64,7 @@ const FIRST_STEP_ORDER = [
64
64
  const cmd = process.argv[2];
65
65
  const args = process.argv.slice(3);
66
66
 
67
+ // Run a command in the current working directory
67
68
  function run(command) {
68
69
  try {
69
70
  execSync(command, { stdio: "inherit" });
@@ -72,6 +73,34 @@ function run(command) {
72
73
  }
73
74
  }
74
75
 
76
+ // Run a command globally (always from HOME) so skills install to ~/.agents/, not cwd
77
+ function runGlobal(command) {
78
+ try {
79
+ execSync(command, { stdio: "inherit", cwd: os.homedir() });
80
+ } catch {
81
+ process.exit(1);
82
+ }
83
+ }
84
+
85
+ const SKILLS_DIR = path.join(os.homedir(), ".agents", "skills");
86
+
87
+ // Return installed weeve lanes by scanning ~/.agents/skills/ for known lane dirs
88
+ function installedLanes() {
89
+ if (!fs.existsSync(SKILLS_DIR)) return {};
90
+ const entries = new Set(fs.readdirSync(SKILLS_DIR));
91
+ const laneMap = {};
92
+ for (const lane of Object.keys(LANES)) {
93
+ if (entries.has(lane)) {
94
+ // Count all skill dirs that start with this lane name
95
+ const count = [...entries].filter(
96
+ (e) => e === lane || e.startsWith(lane + "-")
97
+ ).length;
98
+ laneMap[lane] = { count, source: `keighleykodric/weeve-${lane}` };
99
+ }
100
+ }
101
+ return laneMap;
102
+ }
103
+
75
104
  function installLanes(lanes) {
76
105
  const invalid = lanes.filter((l) => !LANES[l]);
77
106
  if (invalid.length) {
@@ -84,10 +113,10 @@ function installLanes(lanes) {
84
113
  for (const lane of lanes) {
85
114
  const repo = `${ORG}/weeve-${lane}`;
86
115
  console.log(` + ${lane} — ${LANES[lane]}`);
87
- run(`npx skills add ${repo}`);
116
+ runGlobal(`npx skills add ${repo}`);
88
117
  }
89
118
  // Always install framework skills from the weeve repo itself
90
- run(`npx skills add ${ORG}/weeve`);
119
+ runGlobal(`npx skills add ${ORG}/weeve`);
91
120
  // Show the right first step based on what was installed
92
121
  const firstLane = FIRST_STEP_ORDER.find((l) => lanes.includes(l));
93
122
  if (firstLane && firstLane !== "pilot") {
@@ -109,7 +138,6 @@ switch (cmd) {
109
138
  process.exit(1);
110
139
  }
111
140
 
112
- // Check if first arg is a preset
113
141
  if (args.length === 1 && PRESETS[args[0]]) {
114
142
  const preset = args[0];
115
143
  console.log(`Preset: ${preset} (${PRESETS[preset].join(", ")})`);
@@ -120,10 +148,121 @@ switch (cmd) {
120
148
  break;
121
149
  }
122
150
 
151
+ case "update": {
152
+ // Re-install all currently installed lanes to pull the latest skill versions
153
+ const laneMap = installedLanes();
154
+ const installed = Object.keys(laneMap).filter((l) => LANES[l]);
155
+
156
+ if (!installed.length) {
157
+ console.log("\nNo weeve lanes installed. Run 'weeve add <lane>' first.\n");
158
+ break;
159
+ }
160
+
161
+ console.log(`\nUpdating ${installed.length} lane(s)...\n`);
162
+ for (const lane of installed) {
163
+ const repo = `${ORG}/weeve-${lane}`;
164
+ console.log(` ↑ ${lane}`);
165
+ runGlobal(`npx skills add ${repo}`);
166
+ }
167
+ runGlobal(`npx skills add ${ORG}/weeve`);
168
+ console.log(`\nDone. All lanes updated to latest.\n`);
169
+ break;
170
+ }
171
+
172
+ case "remove":
173
+ case "uninstall": {
174
+ if (!args.length) {
175
+ console.error("Usage: weeve remove <lane|preset> [lane2...]");
176
+ process.exit(1);
177
+ }
178
+
179
+ let lanes;
180
+ if (args.length === 1 && PRESETS[args[0]]) {
181
+ const preset = args[0];
182
+ console.log(`Removing preset: ${preset} (${PRESETS[preset].join(", ")})`);
183
+ lanes = PRESETS[preset];
184
+ } else {
185
+ const invalid = args.filter((l) => !LANES[l]);
186
+ if (invalid.length) {
187
+ console.error(`Unknown lane(s): ${invalid.join(", ")}`);
188
+ console.error(`Run 'weeve list' to see available lanes.`);
189
+ process.exit(1);
190
+ }
191
+ lanes = args;
192
+ }
193
+
194
+ console.log(`\nRemoving ${lanes.length} lane(s)...\n`);
195
+ for (const lane of lanes) {
196
+ const repo = `${ORG}/weeve-${lane}`;
197
+ console.log(` - ${lane}`);
198
+ runGlobal(`npx skills remove ${repo}`);
199
+ }
200
+ console.log(`\nDone. Removed: ${lanes.join(", ")}\n`);
201
+ break;
202
+ }
203
+
204
+ case "setup": {
205
+ const configDir = path.join(os.homedir(), ".config", "weeve");
206
+ const configPath = path.join(configDir, "config.yaml");
207
+
208
+ if (fs.existsSync(configPath)) {
209
+ console.log(`\nConfig already exists at ${configPath}\n`);
210
+ console.log(fs.readFileSync(configPath, "utf8"));
211
+ console.log("Delete it and re-run 'weeve setup' to reconfigure.\n");
212
+ break;
213
+ }
214
+
215
+ // Auto-detect common vault locations
216
+ const vaultCandidates = [
217
+ path.join(os.homedir(), "Library/Mobile Documents/iCloud~md~obsidian/Documents"),
218
+ path.join(os.homedir(), "Documents/Obsidian"),
219
+ path.join(os.homedir(), "Obsidian"),
220
+ path.join(os.homedir(), "vault"),
221
+ ];
222
+ const detectedVault = vaultCandidates.find((p) => fs.existsSync(p)) || "";
223
+
224
+ // Auto-detect common repos root
225
+ const reposCandidates = [
226
+ path.join(os.homedir(), "GitHub"),
227
+ path.join(os.homedir(), "code"),
228
+ path.join(os.homedir(), "projects"),
229
+ path.join(os.homedir(), "dev"),
230
+ ];
231
+ const detectedRepos = reposCandidates.find((p) => fs.existsSync(p)) || path.join(os.homedir(), "GitHub");
232
+
233
+ const readline = require("readline");
234
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
235
+
236
+ const vaultPrompt = detectedVault
237
+ ? `Vault path [${detectedVault}]: `
238
+ : `Vault path (Obsidian vault root): `;
239
+
240
+ rl.question(vaultPrompt, (vaultAnswer) => {
241
+ const vaultPath = vaultAnswer.trim() || detectedVault;
242
+ if (!vaultPath) {
243
+ console.error("Vault path is required.");
244
+ rl.close();
245
+ process.exit(1);
246
+ }
247
+
248
+ rl.question(`Repos root [${detectedRepos}]: `, (reposAnswer) => {
249
+ const reposRoot = reposAnswer.trim() || detectedRepos;
250
+ rl.close();
251
+
252
+ fs.mkdirSync(configDir, { recursive: true });
253
+ fs.writeFileSync(configPath, `vault_path: "${vaultPath}"\nrepos_root: "${reposRoot}"\n`);
254
+ console.log(`\nConfig written to ${configPath}`);
255
+ console.log(` vault_path ${vaultPath}`);
256
+ console.log(` repos_root ${reposRoot}`);
257
+ console.log(`\nRun 'weeve add all' to install lanes.\n`);
258
+ });
259
+ });
260
+ break;
261
+ }
262
+
123
263
  case "init": {
124
264
  const presetArg = args[0];
125
265
 
126
- // Resolve lanes to scaffold
127
266
  let lanes;
128
267
  if (!presetArg) {
129
268
  lanes = Object.keys(LANES);
@@ -140,7 +279,6 @@ switch (cmd) {
140
279
  const cwd = process.cwd();
141
280
  const yamlPath = path.join(cwd, "weeve.yaml");
142
281
 
143
- // Bail if weeve.yaml already exists
144
282
  if (fs.existsSync(yamlPath)) {
145
283
  console.error(`Warning: weeve.yaml already exists in ${cwd}. Exiting without changes.`);
146
284
  process.exit(1);
@@ -154,7 +292,6 @@ switch (cmd) {
154
292
  }
155
293
 
156
294
  // 1b. Create loom/ content-type folders based on installed lanes, plus shared/ and archive/
157
- // Loom is organised by content type, not lane — multiple lanes share the same folder.
158
295
  const loomFolders = new Set(["shared"]);
159
296
  const LOOM_MAP = {
160
297
  strategy: ["compass"],
@@ -301,39 +438,10 @@ switch (cmd) {
301
438
  }
302
439
 
303
440
  case "status": {
304
- const lockPath = path.join(os.homedir(), ".claude", "skills-lock.json");
441
+ const laneMap = installedLanes();
305
442
 
306
- if (!fs.existsSync(lockPath)) {
307
- console.log("No weeve lanes installed. Run 'weeve add <lane>' to get started.");
308
- break;
309
- }
310
-
311
- let lock;
312
- try {
313
- lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
314
- } catch {
315
- console.error(`Could not parse ${lockPath}`);
316
- process.exit(1);
317
- }
318
-
319
- const skills = lock.skills || {};
320
-
321
- // Group skills by weeve lane (source matches keighleykodric/weeve-<lane>)
322
- const laneMap = {};
323
- for (const [skillName, skillData] of Object.entries(skills)) {
324
- const source = skillData.source || "";
325
- const match = source.match(/^keighleykodric\/weeve-(.+)$/);
326
- if (match) {
327
- const lane = match[1];
328
- if (!laneMap[lane]) {
329
- laneMap[lane] = { count: 0, source };
330
- }
331
- laneMap[lane].count++;
332
- }
333
- }
334
-
335
- if (Object.keys(laneMap).length === 0) {
336
- console.log("No weeve lanes installed. Run 'weeve add <lane>' to get started.");
443
+ if (!Object.keys(laneMap).length) {
444
+ console.log("\nNo weeve lanes installed. Run 'weeve add <lane>' to get started.\n");
337
445
  break;
338
446
  }
339
447
 
@@ -356,39 +464,6 @@ switch (cmd) {
356
464
  break;
357
465
  }
358
466
 
359
- case "remove":
360
- case "uninstall": {
361
- if (!args.length) {
362
- console.error("Usage: weeve remove <lane|preset> [lane2...]");
363
- process.exit(1);
364
- }
365
-
366
- // Expand preset or validate individual lanes
367
- let lanes;
368
- if (args.length === 1 && PRESETS[args[0]]) {
369
- const preset = args[0];
370
- console.log(`Removing preset: ${preset} (${PRESETS[preset].join(", ")})`);
371
- lanes = PRESETS[preset];
372
- } else {
373
- const invalid = args.filter((l) => !LANES[l]);
374
- if (invalid.length) {
375
- console.error(`Unknown lane(s): ${invalid.join(", ")}`);
376
- console.error(`Run 'weeve list' to see available lanes.`);
377
- process.exit(1);
378
- }
379
- lanes = args;
380
- }
381
-
382
- console.log(`\nRemoving ${lanes.length} lane(s)...\n`);
383
- for (const lane of lanes) {
384
- const repo = `${ORG}/weeve-${lane}`;
385
- console.log(` - ${lane}`);
386
- run(`npx skills remove ${repo}`);
387
- }
388
- console.log(`\nDone. Removed: ${lanes.join(", ")}\n`);
389
- break;
390
- }
391
-
392
467
  case "demo": {
393
468
  const presetArg = args[0];
394
469
 
@@ -430,7 +505,6 @@ switch (cmd) {
430
505
  process.exit(1);
431
506
  }
432
507
 
433
- // Recursively copy demo folder to cwd
434
508
  function copyDir(src, dest) {
435
509
  fs.mkdirSync(dest, { recursive: true });
436
510
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -452,7 +526,7 @@ switch (cmd) {
452
526
  const firstCmd = firstLane && firstLane !== "pilot" ? `/${firstLane}-intro` : `/pilot-intro`;
453
527
 
454
528
  console.log(`\nDemo ready: ${demoName} (${presetArg} preset)\n`);
455
- console.log(` weeve.yaml project config (docs_root: ./docs)`);
529
+ console.log(` weeve.yaml project config`);
456
530
  console.log(` docs/shared/ weeve-project.md, weeve-guardrails.md, weeve-intake.md\n`);
457
531
  console.log(`Next steps:`);
458
532
  console.log(` 1. weeve add ${presetArg} — install the lanes`);
@@ -464,7 +538,6 @@ switch (cmd) {
464
538
  const subcmd = args[0];
465
539
 
466
540
  if (!subcmd || subcmd === "show") {
467
- // Walk up from cwd to find weeve.yaml
468
541
  let dir = process.cwd();
469
542
  let found = null;
470
543
  for (let i = 0; i < 4; i++) {
@@ -487,7 +560,6 @@ switch (cmd) {
487
560
  const raw = fs.readFileSync(found, "utf8");
488
561
  console.log(`\nProject config: ${found}\n`);
489
562
 
490
- // Parse and display key fields
491
563
  const nameMatch = raw.match(/^\s{2}name:\s*(.+)$/m);
492
564
  const tagMatch = raw.match(/^\s{2}tag:\s*(.+)$/m);
493
565
  const docsMatch = raw.match(/^\s{2}docs_root:\s*(.+)$/m);
@@ -495,22 +567,35 @@ switch (cmd) {
495
567
 
496
568
  const name = nameMatch ? nameMatch[1].trim() : "(not set)";
497
569
  const tag = tagMatch ? tagMatch[1].trim() : "(not set)";
498
- const docsRoot = docsMatch ? docsMatch[1].trim() : `<vault>/Projects/${tag}/docs (via Claudette)`;
499
- const schema = schemaMatch ? schemaMatch[1].trim() : "(not set)";
570
+ const schemaVersion = schemaMatch ? schemaMatch[1].trim() : "(not set)";
571
+
572
+ // Resolve docs_root: explicit in weeve.yaml > weeve config > default
573
+ let docsRoot;
574
+ if (docsMatch) {
575
+ docsRoot = docsMatch[1].trim();
576
+ } else {
577
+ const weeveConfig = path.join(os.homedir(), ".config", "weeve", "config.yaml");
578
+ if (fs.existsSync(weeveConfig)) {
579
+ const cfgRaw = fs.readFileSync(weeveConfig, "utf8");
580
+ const vaultMatch = cfgRaw.match(/^vault_path:\s*"?(.+?)"?\s*$/m);
581
+ const vault = vaultMatch ? vaultMatch[1] : null;
582
+ docsRoot = vault ? `${vault}/Projects/${tag}/weeve` : `<vault>/Projects/${tag}/weeve`;
583
+ } else {
584
+ docsRoot = `<vault>/Projects/${tag}/weeve (run 'weeve setup' to configure)`;
585
+ }
586
+ }
500
587
 
501
588
  const maxLabel = 14;
502
589
  console.log(` ${"name".padEnd(maxLabel)}${name}`);
503
590
  console.log(` ${"tag".padEnd(maxLabel)}${tag}`);
504
591
  console.log(` ${"docs_root".padEnd(maxLabel)}${docsRoot}`);
505
- console.log(` ${"schema_version".padEnd(maxLabel)}${schema}`);
592
+ console.log(` ${"schema_version".padEnd(maxLabel)}${schemaVersion}`);
506
593
 
507
- // Check for repos
508
594
  const reposBlock = raw.match(/^\s{2}repos:\n((?:\s{4}.+\n?)*)/m);
509
595
  if (reposBlock) {
510
596
  console.log(` ${"repos".padEnd(maxLabel)}${reposBlock[1].trim().split("\n").length} registered`);
511
597
  }
512
598
 
513
- // Check for depends_on
514
599
  const depsBlock = raw.match(/^\s{2}depends_on:\n((?:\s{4}.+\n?)*)/m);
515
600
  if (depsBlock) {
516
601
  const depTags = [...depsBlock[1].matchAll(/tag:\s*(\S+)/g)].map((m) => m[1]);
@@ -519,13 +604,6 @@ switch (cmd) {
519
604
  console.log(` ${"depends_on".padEnd(maxLabel)}${depList}`);
520
605
  }
521
606
 
522
- // Check for shared docs
523
- const sharedDir = path.join(path.dirname(found), "docs", "shared");
524
- if (fs.existsSync(sharedDir)) {
525
- const sharedFiles = fs.readdirSync(sharedDir).filter((f) => f.endsWith(".md"));
526
- console.log(` ${"shared/".padEnd(maxLabel)}${sharedFiles.join(", ")}`);
527
- }
528
-
529
607
  console.log();
530
608
  break;
531
609
  }
@@ -570,8 +648,10 @@ switch (cmd) {
570
648
  Cross-functional contract framework
571
649
 
572
650
  Commands:
651
+ weeve setup Configure weeve (vault path, repos root)
573
652
  weeve add <lane> [lane2...] Install one or more lanes
574
653
  weeve add <preset> Install a preset group of lanes
654
+ weeve update Update all installed lanes to latest
575
655
  weeve remove <lane> [lane2…] Remove one or more lanes
576
656
  weeve remove <preset> Remove a preset group of lanes
577
657
  weeve init [preset] Scaffold weeve structure in current directory
@@ -582,9 +662,11 @@ Commands:
582
662
  weeve presets Show preset groups
583
663
 
584
664
  Examples:
665
+ weeve setup First-time setup — configure vault and repos root
585
666
  weeve add compass ally pilot Install three lanes
586
667
  weeve add core Install core preset (compass, layers-plus, ally, rubric, pilot)
587
668
  weeve add all Install everything
669
+ weeve update Pull latest versions of all installed lanes
588
670
  weeve init core Scaffold folder structure for the core preset
589
671
  weeve demo core Copy the Prism demo project (ready to run immediately)
590
672
  weeve status See which lanes are installed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keighleykodric/weeve",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Cross-functional contract framework — install and manage Weeve lanes",
5
5
  "bin": {
6
6
  "weeve": "./bin/weeve.js"