@keighleykodric/weeve 0.1.2 → 0.1.3

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 +180 -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,45 @@ 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
+ // Lock file written by the skills CLI — lives in ~/.claude/ regardless of cwd
86
+ const LOCK_PATH = path.join(os.homedir(), ".claude", "skills-lock.json");
87
+
88
+ function readLock() {
89
+ if (!fs.existsSync(LOCK_PATH)) return null;
90
+ try {
91
+ return JSON.parse(fs.readFileSync(LOCK_PATH, "utf8"));
92
+ } catch {
93
+ console.error(`Could not parse ${LOCK_PATH}`);
94
+ process.exit(1);
95
+ }
96
+ }
97
+
98
+ // Return installed weeve lanes from the lock file { lane: { count, source } }
99
+ function installedLanes() {
100
+ const lock = readLock();
101
+ if (!lock) return {};
102
+ const laneMap = {};
103
+ for (const [skillName, skillData] of Object.entries(lock.skills || {})) {
104
+ const source = skillData.source || "";
105
+ const match = source.match(/^keighleykodric\/weeve-(.+)$/);
106
+ if (match) {
107
+ const lane = match[1];
108
+ if (!laneMap[lane]) laneMap[lane] = { count: 0, source };
109
+ laneMap[lane].count++;
110
+ }
111
+ }
112
+ return laneMap;
113
+ }
114
+
75
115
  function installLanes(lanes) {
76
116
  const invalid = lanes.filter((l) => !LANES[l]);
77
117
  if (invalid.length) {
@@ -84,10 +124,10 @@ function installLanes(lanes) {
84
124
  for (const lane of lanes) {
85
125
  const repo = `${ORG}/weeve-${lane}`;
86
126
  console.log(` + ${lane} — ${LANES[lane]}`);
87
- run(`npx skills add ${repo}`);
127
+ runGlobal(`npx skills add ${repo}`);
88
128
  }
89
129
  // Always install framework skills from the weeve repo itself
90
- run(`npx skills add ${ORG}/weeve`);
130
+ runGlobal(`npx skills add ${ORG}/weeve`);
91
131
  // Show the right first step based on what was installed
92
132
  const firstLane = FIRST_STEP_ORDER.find((l) => lanes.includes(l));
93
133
  if (firstLane && firstLane !== "pilot") {
@@ -109,7 +149,6 @@ switch (cmd) {
109
149
  process.exit(1);
110
150
  }
111
151
 
112
- // Check if first arg is a preset
113
152
  if (args.length === 1 && PRESETS[args[0]]) {
114
153
  const preset = args[0];
115
154
  console.log(`Preset: ${preset} (${PRESETS[preset].join(", ")})`);
@@ -120,10 +159,121 @@ switch (cmd) {
120
159
  break;
121
160
  }
122
161
 
162
+ case "update": {
163
+ // Re-install all currently installed lanes to pull the latest skill versions
164
+ const laneMap = installedLanes();
165
+ const installed = Object.keys(laneMap).filter((l) => LANES[l]);
166
+
167
+ if (!installed.length) {
168
+ console.log("\nNo weeve lanes installed. Run 'weeve add <lane>' first.\n");
169
+ break;
170
+ }
171
+
172
+ console.log(`\nUpdating ${installed.length} lane(s)...\n`);
173
+ for (const lane of installed) {
174
+ const repo = `${ORG}/weeve-${lane}`;
175
+ console.log(` ↑ ${lane}`);
176
+ runGlobal(`npx skills add ${repo}`);
177
+ }
178
+ runGlobal(`npx skills add ${ORG}/weeve`);
179
+ console.log(`\nDone. All lanes updated to latest.\n`);
180
+ break;
181
+ }
182
+
183
+ case "remove":
184
+ case "uninstall": {
185
+ if (!args.length) {
186
+ console.error("Usage: weeve remove <lane|preset> [lane2...]");
187
+ process.exit(1);
188
+ }
189
+
190
+ let lanes;
191
+ if (args.length === 1 && PRESETS[args[0]]) {
192
+ const preset = args[0];
193
+ console.log(`Removing preset: ${preset} (${PRESETS[preset].join(", ")})`);
194
+ lanes = PRESETS[preset];
195
+ } else {
196
+ const invalid = args.filter((l) => !LANES[l]);
197
+ if (invalid.length) {
198
+ console.error(`Unknown lane(s): ${invalid.join(", ")}`);
199
+ console.error(`Run 'weeve list' to see available lanes.`);
200
+ process.exit(1);
201
+ }
202
+ lanes = args;
203
+ }
204
+
205
+ console.log(`\nRemoving ${lanes.length} lane(s)...\n`);
206
+ for (const lane of lanes) {
207
+ const repo = `${ORG}/weeve-${lane}`;
208
+ console.log(` - ${lane}`);
209
+ runGlobal(`npx skills remove ${repo}`);
210
+ }
211
+ console.log(`\nDone. Removed: ${lanes.join(", ")}\n`);
212
+ break;
213
+ }
214
+
215
+ case "setup": {
216
+ const configDir = path.join(os.homedir(), ".config", "weeve");
217
+ const configPath = path.join(configDir, "config.yaml");
218
+
219
+ if (fs.existsSync(configPath)) {
220
+ console.log(`\nConfig already exists at ${configPath}\n`);
221
+ console.log(fs.readFileSync(configPath, "utf8"));
222
+ console.log("Delete it and re-run 'weeve setup' to reconfigure.\n");
223
+ break;
224
+ }
225
+
226
+ // Auto-detect common vault locations
227
+ const vaultCandidates = [
228
+ path.join(os.homedir(), "Library/Mobile Documents/iCloud~md~obsidian/Documents"),
229
+ path.join(os.homedir(), "Documents/Obsidian"),
230
+ path.join(os.homedir(), "Obsidian"),
231
+ path.join(os.homedir(), "vault"),
232
+ ];
233
+ const detectedVault = vaultCandidates.find((p) => fs.existsSync(p)) || "";
234
+
235
+ // Auto-detect common repos root
236
+ const reposCandidates = [
237
+ path.join(os.homedir(), "GitHub"),
238
+ path.join(os.homedir(), "code"),
239
+ path.join(os.homedir(), "projects"),
240
+ path.join(os.homedir(), "dev"),
241
+ ];
242
+ const detectedRepos = reposCandidates.find((p) => fs.existsSync(p)) || path.join(os.homedir(), "GitHub");
243
+
244
+ const readline = require("readline");
245
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
246
+
247
+ const vaultPrompt = detectedVault
248
+ ? `Vault path [${detectedVault}]: `
249
+ : `Vault path (Obsidian vault root): `;
250
+
251
+ rl.question(vaultPrompt, (vaultAnswer) => {
252
+ const vaultPath = vaultAnswer.trim() || detectedVault;
253
+ if (!vaultPath) {
254
+ console.error("Vault path is required.");
255
+ rl.close();
256
+ process.exit(1);
257
+ }
258
+
259
+ rl.question(`Repos root [${detectedRepos}]: `, (reposAnswer) => {
260
+ const reposRoot = reposAnswer.trim() || detectedRepos;
261
+ rl.close();
262
+
263
+ fs.mkdirSync(configDir, { recursive: true });
264
+ fs.writeFileSync(configPath, `vault_path: "${vaultPath}"\nrepos_root: "${reposRoot}"\n`);
265
+ console.log(`\nConfig written to ${configPath}`);
266
+ console.log(` vault_path ${vaultPath}`);
267
+ console.log(` repos_root ${reposRoot}`);
268
+ console.log(`\nRun 'weeve add all' to install lanes.\n`);
269
+ });
270
+ });
271
+ break;
272
+ }
273
+
123
274
  case "init": {
124
275
  const presetArg = args[0];
125
276
 
126
- // Resolve lanes to scaffold
127
277
  let lanes;
128
278
  if (!presetArg) {
129
279
  lanes = Object.keys(LANES);
@@ -140,7 +290,6 @@ switch (cmd) {
140
290
  const cwd = process.cwd();
141
291
  const yamlPath = path.join(cwd, "weeve.yaml");
142
292
 
143
- // Bail if weeve.yaml already exists
144
293
  if (fs.existsSync(yamlPath)) {
145
294
  console.error(`Warning: weeve.yaml already exists in ${cwd}. Exiting without changes.`);
146
295
  process.exit(1);
@@ -154,7 +303,6 @@ switch (cmd) {
154
303
  }
155
304
 
156
305
  // 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
306
  const loomFolders = new Set(["shared"]);
159
307
  const LOOM_MAP = {
160
308
  strategy: ["compass"],
@@ -301,39 +449,10 @@ switch (cmd) {
301
449
  }
302
450
 
303
451
  case "status": {
304
- const lockPath = path.join(os.homedir(), ".claude", "skills-lock.json");
305
-
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
- }
452
+ const laneMap = installedLanes();
318
453
 
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.");
454
+ if (!Object.keys(laneMap).length) {
455
+ console.log("\nNo weeve lanes installed. Run 'weeve add <lane>' to get started.\n");
337
456
  break;
338
457
  }
339
458
 
@@ -356,39 +475,6 @@ switch (cmd) {
356
475
  break;
357
476
  }
358
477
 
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
478
  case "demo": {
393
479
  const presetArg = args[0];
394
480
 
@@ -430,7 +516,6 @@ switch (cmd) {
430
516
  process.exit(1);
431
517
  }
432
518
 
433
- // Recursively copy demo folder to cwd
434
519
  function copyDir(src, dest) {
435
520
  fs.mkdirSync(dest, { recursive: true });
436
521
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -452,7 +537,7 @@ switch (cmd) {
452
537
  const firstCmd = firstLane && firstLane !== "pilot" ? `/${firstLane}-intro` : `/pilot-intro`;
453
538
 
454
539
  console.log(`\nDemo ready: ${demoName} (${presetArg} preset)\n`);
455
- console.log(` weeve.yaml project config (docs_root: ./docs)`);
540
+ console.log(` weeve.yaml project config`);
456
541
  console.log(` docs/shared/ weeve-project.md, weeve-guardrails.md, weeve-intake.md\n`);
457
542
  console.log(`Next steps:`);
458
543
  console.log(` 1. weeve add ${presetArg} — install the lanes`);
@@ -464,7 +549,6 @@ switch (cmd) {
464
549
  const subcmd = args[0];
465
550
 
466
551
  if (!subcmd || subcmd === "show") {
467
- // Walk up from cwd to find weeve.yaml
468
552
  let dir = process.cwd();
469
553
  let found = null;
470
554
  for (let i = 0; i < 4; i++) {
@@ -487,7 +571,6 @@ switch (cmd) {
487
571
  const raw = fs.readFileSync(found, "utf8");
488
572
  console.log(`\nProject config: ${found}\n`);
489
573
 
490
- // Parse and display key fields
491
574
  const nameMatch = raw.match(/^\s{2}name:\s*(.+)$/m);
492
575
  const tagMatch = raw.match(/^\s{2}tag:\s*(.+)$/m);
493
576
  const docsMatch = raw.match(/^\s{2}docs_root:\s*(.+)$/m);
@@ -495,22 +578,35 @@ switch (cmd) {
495
578
 
496
579
  const name = nameMatch ? nameMatch[1].trim() : "(not set)";
497
580
  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)";
581
+ const schemaVersion = schemaMatch ? schemaMatch[1].trim() : "(not set)";
582
+
583
+ // Resolve docs_root: explicit in weeve.yaml > weeve config > default
584
+ let docsRoot;
585
+ if (docsMatch) {
586
+ docsRoot = docsMatch[1].trim();
587
+ } else {
588
+ const weeveConfig = path.join(os.homedir(), ".config", "weeve", "config.yaml");
589
+ if (fs.existsSync(weeveConfig)) {
590
+ const cfgRaw = fs.readFileSync(weeveConfig, "utf8");
591
+ const vaultMatch = cfgRaw.match(/^vault_path:\s*"?(.+?)"?\s*$/m);
592
+ const vault = vaultMatch ? vaultMatch[1] : null;
593
+ docsRoot = vault ? `${vault}/Projects/${tag}/weeve` : `<vault>/Projects/${tag}/weeve`;
594
+ } else {
595
+ docsRoot = `<vault>/Projects/${tag}/weeve (run 'weeve setup' to configure)`;
596
+ }
597
+ }
500
598
 
501
599
  const maxLabel = 14;
502
600
  console.log(` ${"name".padEnd(maxLabel)}${name}`);
503
601
  console.log(` ${"tag".padEnd(maxLabel)}${tag}`);
504
602
  console.log(` ${"docs_root".padEnd(maxLabel)}${docsRoot}`);
505
- console.log(` ${"schema_version".padEnd(maxLabel)}${schema}`);
603
+ console.log(` ${"schema_version".padEnd(maxLabel)}${schemaVersion}`);
506
604
 
507
- // Check for repos
508
605
  const reposBlock = raw.match(/^\s{2}repos:\n((?:\s{4}.+\n?)*)/m);
509
606
  if (reposBlock) {
510
607
  console.log(` ${"repos".padEnd(maxLabel)}${reposBlock[1].trim().split("\n").length} registered`);
511
608
  }
512
609
 
513
- // Check for depends_on
514
610
  const depsBlock = raw.match(/^\s{2}depends_on:\n((?:\s{4}.+\n?)*)/m);
515
611
  if (depsBlock) {
516
612
  const depTags = [...depsBlock[1].matchAll(/tag:\s*(\S+)/g)].map((m) => m[1]);
@@ -519,13 +615,6 @@ switch (cmd) {
519
615
  console.log(` ${"depends_on".padEnd(maxLabel)}${depList}`);
520
616
  }
521
617
 
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
618
  console.log();
530
619
  break;
531
620
  }
@@ -570,8 +659,10 @@ switch (cmd) {
570
659
  Cross-functional contract framework
571
660
 
572
661
  Commands:
662
+ weeve setup Configure weeve (vault path, repos root)
573
663
  weeve add <lane> [lane2...] Install one or more lanes
574
664
  weeve add <preset> Install a preset group of lanes
665
+ weeve update Update all installed lanes to latest
575
666
  weeve remove <lane> [lane2…] Remove one or more lanes
576
667
  weeve remove <preset> Remove a preset group of lanes
577
668
  weeve init [preset] Scaffold weeve structure in current directory
@@ -582,9 +673,11 @@ Commands:
582
673
  weeve presets Show preset groups
583
674
 
584
675
  Examples:
676
+ weeve setup First-time setup — configure vault and repos root
585
677
  weeve add compass ally pilot Install three lanes
586
678
  weeve add core Install core preset (compass, layers-plus, ally, rubric, pilot)
587
679
  weeve add all Install everything
680
+ weeve update Pull latest versions of all installed lanes
588
681
  weeve init core Scaffold folder structure for the core preset
589
682
  weeve demo core Copy the Prism demo project (ready to run immediately)
590
683
  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.3",
4
4
  "description": "Cross-functional contract framework — install and manage Weeve lanes",
5
5
  "bin": {
6
6
  "weeve": "./bin/weeve.js"