skillwiki 0.2.0-beta.20 → 0.2.0-beta.23

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/dist/cli.js CHANGED
@@ -71,7 +71,8 @@ var ExitCode = {
71
71
  ARCHIVE_ALREADY_ARCHIVED: 31,
72
72
  DRIFT_DETECTED: 32,
73
73
  RAW_DEDUP_DETECTED: 33,
74
- MIGRATION_APPLIED: 34
74
+ MIGRATION_APPLIED: 34,
75
+ UNKNOWN_WIKI_PROFILE: 35
75
76
  };
76
77
 
77
78
  // ../shared/src/json-output.ts
@@ -504,6 +505,16 @@ import { readFile as readFile4, writeFile as writeFile2, mkdir as mkdir2 } from
504
505
  import { dirname as dirname2 } from "path";
505
506
  var CONFIG_KEYS = ["WIKI_PATH", "WIKI_LANG"];
506
507
  var _whitelist = new Set(CONFIG_KEYS);
508
+ var PROFILE_PATH_RE = /^WIKI_([A-Z][A-Z0-9_]{0,31})_PATH$/;
509
+ var PROFILE_LANG_RE = /^WIKI_([A-Z][A-Z0-9_]{0,31})_LANG$/;
510
+ var PROFILE_DEFAULT_RE = /^WIKI_DEFAULT$/;
511
+ function isValidWikiProfileKey(key) {
512
+ if (key === "WIKI_PATH" || key === "WIKI_LANG") return false;
513
+ return PROFILE_PATH_RE.test(key) || PROFILE_LANG_RE.test(key) || PROFILE_DEFAULT_RE.test(key);
514
+ }
515
+ function profileKey(name, suffix) {
516
+ return `WIKI_${name.toUpperCase().replace(/-/g, "_").replace(/[^A-Z0-9_]/g, "")}_${suffix}`;
517
+ }
507
518
  function parseDotenvText(text) {
508
519
  const out = {};
509
520
  for (const rawLine of text.split(/\r?\n/)) {
@@ -513,7 +524,7 @@ function parseDotenvText(text) {
513
524
  if (eq <= 0) continue;
514
525
  const key = line.slice(0, eq).trim();
515
526
  const value = line.slice(eq + 1).trim();
516
- if (!_whitelist.has(key)) continue;
527
+ if (!_whitelist.has(key) && !isValidWikiProfileKey(key)) continue;
517
528
  if (value.length === 0) continue;
518
529
  out[key] = value;
519
530
  }
@@ -598,6 +609,14 @@ async function resolveInitTimePath(input) {
598
609
  return { path: hermes.WIKI_PATH, source: "hermes-dotenv", ...input.explain ? { chain } : {} };
599
610
  }
600
611
  if (input.explain) chain.push({ source: "hermes-dotenv", matched: false });
612
+ if (input.cwd) {
613
+ const projCfg = await parseDotenvFile(join2(input.cwd, ".skillwiki", ".env"));
614
+ if (projCfg.WIKI_PATH !== void 0) {
615
+ if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
616
+ return { path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} };
617
+ }
618
+ }
619
+ if (input.explain) chain.push({ source: "project-dotenv", matched: false });
601
620
  const fallback = join2(input.home, "wiki");
602
621
  if (input.explain) chain.push({ source: "default", matched: true, value: fallback });
603
622
  return { path: fallback, source: "default", ...input.explain ? { chain } : {} };
@@ -609,15 +628,73 @@ async function resolveRuntimePath(input) {
609
628
  return ok({ path: input.flag, source: "flag", ...input.explain ? { chain } : {} });
610
629
  }
611
630
  if (input.explain) chain.push({ source: "flag", matched: false });
631
+ const swGlobal = await parseDotenvFile(join2(input.home, ".skillwiki", ".env"));
632
+ const wikiName = input.wiki;
633
+ if (wikiName !== void 0 && wikiName.length > 0) {
634
+ if (wikiName.toLowerCase() === "default") {
635
+ const path2 = swGlobal.WIKI_PATH;
636
+ if (path2 !== void 0) {
637
+ if (input.explain) chain.push({ source: "wiki-profile", matched: true, value: path2 });
638
+ return ok({ path: path2, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} });
639
+ }
640
+ if (input.explain) chain.push({ source: "wiki-profile", matched: false });
641
+ return err("UNKNOWN_WIKI_PROFILE", {
642
+ message: `Wiki profile "default" not found. Set it with: skillwiki config set wiki.path <dir>`
643
+ });
644
+ }
645
+ const key = profileKey(wikiName, "PATH");
646
+ const path = swGlobal[key];
647
+ if (path !== void 0) {
648
+ if (input.explain) chain.push({ source: "wiki-profile", matched: true, value: path });
649
+ return ok({ path, source: "wiki-profile", ...input.explain ? { chain } : {} });
650
+ }
651
+ if (input.explain) chain.push({ source: "wiki-profile", matched: false });
652
+ return err("UNKNOWN_WIKI_PROFILE", {
653
+ message: `Wiki profile "${wikiName}" not found. Set it with: skillwiki config set wiki.${wikiName}.path <dir>`
654
+ });
655
+ }
656
+ if (input.wikiEnv !== void 0 && input.wikiEnv.length > 0) {
657
+ const key = profileKey(input.wikiEnv, "PATH");
658
+ const path = swGlobal[key];
659
+ if (path !== void 0) {
660
+ if (input.explain) chain.push({ source: "wiki-profile", matched: true, value: path });
661
+ return ok({ path, source: "wiki-profile", ...input.explain ? { chain } : {} });
662
+ }
663
+ if (input.explain) chain.push({ source: "wiki-profile", matched: false });
664
+ return err("UNKNOWN_WIKI_PROFILE", {
665
+ message: `Wiki profile "${input.wikiEnv}" not found (from $WIKI env). Set it with: skillwiki config set wiki.${input.wikiEnv}.path <dir>`
666
+ });
667
+ }
668
+ if (input.explain) chain.push({ source: "wiki-profile", matched: false });
612
669
  if (input.envValue !== void 0 && input.envValue.length > 0) {
613
670
  if (input.explain) chain.push({ source: "env", matched: true, value: input.envValue });
614
671
  return ok({ path: input.envValue, source: "env", ...input.explain ? { chain } : {} });
615
672
  }
616
673
  if (input.explain) chain.push({ source: "env", matched: false });
617
- const sw = await parseDotenvFile(join2(input.home, ".skillwiki", ".env"));
618
- if (sw.WIKI_PATH !== void 0) {
619
- if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: true, value: sw.WIKI_PATH });
620
- return ok({ path: sw.WIKI_PATH, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} });
674
+ if (input.cwd) {
675
+ const projCfg = await parseDotenvFile(join2(input.cwd, ".skillwiki", ".env"));
676
+ if (projCfg.WIKI_PATH !== void 0) {
677
+ if (input.explain) chain.push({ source: "project-dotenv", matched: true, value: projCfg.WIKI_PATH });
678
+ return ok({ path: projCfg.WIKI_PATH, source: "project-dotenv", ...input.explain ? { chain } : {} });
679
+ }
680
+ if (input.explain) chain.push({ source: "project-dotenv", matched: false });
681
+ }
682
+ const defaultProfile = swGlobal["WIKI_DEFAULT"];
683
+ if (defaultProfile !== void 0) {
684
+ const key = profileKey(defaultProfile, "PATH");
685
+ const path = swGlobal[key];
686
+ if (path !== void 0) {
687
+ if (input.explain) chain.push({ source: "wiki-default", matched: true, value: path });
688
+ return ok({ path, source: "wiki-default", ...input.explain ? { chain } : {} });
689
+ }
690
+ if (input.explain) chain.push({ source: "wiki-default", matched: false });
691
+ return err("UNKNOWN_WIKI_PROFILE", {
692
+ message: `Default wiki profile "${defaultProfile}" not found. Set it with: skillwiki config set wiki.${defaultProfile}.path <dir>`
693
+ });
694
+ }
695
+ if (swGlobal.WIKI_PATH !== void 0) {
696
+ if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: true, value: swGlobal.WIKI_PATH });
697
+ return ok({ path: swGlobal.WIKI_PATH, source: "skillwiki-dotenv", ...input.explain ? { chain } : {} });
621
698
  }
622
699
  if (input.explain) chain.push({ source: "skillwiki-dotenv", matched: false });
623
700
  return err("NO_VAULT_CONFIGURED", {
@@ -631,8 +708,11 @@ async function runOrphans(input) {
631
708
  if (input.vault) {
632
709
  vault = input.vault;
633
710
  } else {
634
- const r = await resolveRuntimePath({ flag: void 0, envValue: input.envValue, home: input.home ?? "" });
635
- if (!r.ok) return { exitCode: ExitCode.NO_VAULT_CONFIGURED, result: r };
711
+ const r = await resolveRuntimePath({ flag: void 0, envValue: input.envValue, home: input.home ?? "", wiki: input.wiki });
712
+ if (!r.ok) {
713
+ const exitCode = r.error === "UNKNOWN_WIKI_PROFILE" ? ExitCode.UNKNOWN_WIKI_PROFILE : ExitCode.NO_VAULT_CONFIGURED;
714
+ return { exitCode, result: r };
715
+ }
636
716
  vault = r.data.path;
637
717
  }
638
718
  const scan = await scanVault(vault);
@@ -953,9 +1033,13 @@ async function runPath(input) {
953
1033
  flag: input.flag,
954
1034
  envValue: input.envValue,
955
1035
  home: input.home,
1036
+ wiki: input.wiki,
956
1037
  explain: input.explain
957
1038
  });
958
- if (!r.ok) return { exitCode: ExitCode.NO_VAULT_CONFIGURED, result: r };
1039
+ if (!r.ok) {
1040
+ const exitCode = r.error === "UNKNOWN_WIKI_PROFILE" ? ExitCode.UNKNOWN_WIKI_PROFILE : ExitCode.NO_VAULT_CONFIGURED;
1041
+ return { exitCode, result: r };
1042
+ }
959
1043
  return { exitCode: ExitCode.OK, result: ok({ path: r.data.path, source: r.data.source, ...r.data.chain ? { chain: r.data.chain } : {}, humanHint: `${r.data.path} (via ${r.data.source})` }) };
960
1044
  }
961
1045
 
@@ -1124,13 +1208,13 @@ async function runInit(input) {
1124
1208
  }
1125
1209
  const existingEnv = parseDotenvText(existingEnvRaw);
1126
1210
  const swDotenvHadPath = existingEnv.WIKI_PATH !== void 0;
1127
- if (existingEnv.WIKI_PATH !== void 0 && existingEnv.WIKI_PATH !== target && !input.force) {
1211
+ if (!input.profile && existingEnv.WIKI_PATH !== void 0 && existingEnv.WIKI_PATH !== target && !input.force) {
1128
1212
  return {
1129
1213
  exitCode: ExitCode.ENV_WRITE_CONFLICT,
1130
1214
  result: err("ENV_WRITE_CONFLICT", { key: "WIKI_PATH", existing: existingEnv.WIKI_PATH, attempted: target })
1131
1215
  };
1132
1216
  }
1133
- if (existingEnv.WIKI_LANG !== void 0 && existingEnv.WIKI_LANG !== canonicalLang && !input.force) {
1217
+ if (!input.profile && existingEnv.WIKI_LANG !== void 0 && existingEnv.WIKI_LANG !== canonicalLang && !input.force) {
1134
1218
  return {
1135
1219
  exitCode: ExitCode.ENV_WRITE_CONFLICT,
1136
1220
  result: err("ENV_WRITE_CONFLICT", { key: "WIKI_LANG", existing: existingEnv.WIKI_LANG, attempted: canonicalLang })
@@ -1209,7 +1293,16 @@ async function runInit(input) {
1209
1293
  let envWritten = "";
1210
1294
  if (!skipEnv) {
1211
1295
  try {
1212
- await writeDotenv(envPath, { WIKI_PATH: target, WIKI_LANG: canonicalLang }, existingEnvRaw);
1296
+ const envEntries = {};
1297
+ if (input.profile) {
1298
+ envEntries[profileKey(input.profile, "PATH")] = target;
1299
+ envEntries[profileKey(input.profile, "LANG")] = canonicalLang;
1300
+ envEntries["WIKI_DEFAULT"] = input.profile;
1301
+ } else {
1302
+ envEntries["WIKI_PATH"] = target;
1303
+ envEntries["WIKI_LANG"] = canonicalLang;
1304
+ }
1305
+ await writeDotenv(envPath, envEntries, existingEnvRaw);
1213
1306
  envWritten = envPath;
1214
1307
  } catch (e) {
1215
1308
  return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: envPath, message: String(e) }) };
@@ -1525,8 +1618,19 @@ async function runDedup(input) {
1525
1618
  // src/commands/lint.ts
1526
1619
  var STRUCT_MIN_BODY_LINES = 60;
1527
1620
  var STRUCT_MIN_SECTIONS = 3;
1621
+ function hasDuplicateFrontmatter(body) {
1622
+ if (/^---\r?\n/.test(body)) return true;
1623
+ const lines = body.split(/\r?\n/);
1624
+ const limit = Math.min(lines.length, 20);
1625
+ let seenYamlKey = false;
1626
+ for (let i = 0; i < limit; i++) {
1627
+ if (/^\w[\w-]*:/.test(lines[i].trim())) seenYamlKey = true;
1628
+ if (seenYamlKey && lines[i].trim() === "---") return true;
1629
+ }
1630
+ return false;
1631
+ }
1528
1632
  var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "tag_not_in_taxonomy"];
1529
- var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "contested", "orphans", "legacy_citation_style", "orphaned_citations"];
1633
+ var WARNING_ORDER = ["index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "contested", "orphans", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter"];
1530
1634
  var INFO_ORDER = ["bridges", "low_confidence_single_source", "page_structure", "topic_map_recommended"];
1531
1635
  async function runLint(input) {
1532
1636
  const buckets = {};
@@ -1575,11 +1679,13 @@ async function runLint(input) {
1575
1679
  const legacyPages = [];
1576
1680
  const orphanedPages = [];
1577
1681
  const structFlags = [];
1682
+ const dupFrontmatter = [];
1578
1683
  for (const page of scan.data.typedKnowledge) {
1579
1684
  const text = await readPage(page);
1580
1685
  const split = splitFrontmatter(text);
1581
1686
  if (!split.ok) continue;
1582
1687
  const body = split.data.body;
1688
+ if (hasDuplicateFrontmatter(body)) dupFrontmatter.push(page.relPath);
1583
1689
  if (isLegacyCitationStyle(body)) legacyPages.push(page.relPath);
1584
1690
  if (hasOrphanedCitations(body)) orphanedPages.push(page.relPath);
1585
1691
  const bodyLines = body.split("\n").filter((l) => l.trim().length > 0).length;
@@ -1599,6 +1705,7 @@ async function runLint(input) {
1599
1705
  if (legacyPages.length > 0) buckets.legacy_citation_style = legacyPages;
1600
1706
  if (orphanedPages.length > 0) buckets.orphaned_citations = orphanedPages;
1601
1707
  if (structFlags.length > 0) buckets.page_structure = structFlags;
1708
+ if (dupFrontmatter.length > 0) buckets.duplicate_frontmatter = dupFrontmatter;
1602
1709
  }
1603
1710
  const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
1604
1711
  const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
@@ -1636,7 +1743,7 @@ import { readFile as readFile12 } from "fs/promises";
1636
1743
  import { existsSync } from "fs";
1637
1744
  import { join as join13 } from "path";
1638
1745
  function validateKey(key) {
1639
- return CONFIG_KEYS.includes(key);
1746
+ return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
1640
1747
  }
1641
1748
  function configPath(home) {
1642
1749
  return join13(home, ".skillwiki", ".env");
@@ -1671,7 +1778,21 @@ async function runConfigSet(input) {
1671
1778
  async function runConfigList(input) {
1672
1779
  const map = await parseDotenvFile(configPath(input.home));
1673
1780
  const entries = Object.entries(map).map(([key, value]) => ({ key, value: value ?? "" }));
1674
- return { exitCode: ExitCode.OK, result: ok({ entries, humanHint: entries.map((e) => `${e.key}=${e.value}`).join("\n") }) };
1781
+ let profiles;
1782
+ if (input.profiles) {
1783
+ const defaultProfile = map["WIKI_DEFAULT"];
1784
+ profiles = [];
1785
+ for (const key of Object.keys(map)) {
1786
+ const m = key.match(/^WIKI_([A-Z][A-Z0-9_]{0,31})_PATH$/);
1787
+ if (m && key !== "WIKI_PATH") {
1788
+ const name = m[1].toLowerCase().replace(/_/g, "-");
1789
+ profiles.push({ name, path: map[key] ?? "", isDefault: name === defaultProfile });
1790
+ }
1791
+ }
1792
+ profiles.sort((a, b) => a.name.localeCompare(b.name));
1793
+ }
1794
+ const hint = profiles ? profiles.map((p) => `${p.isDefault ? "* " : " "}${p.name} \u2192 ${p.path}`).join("\n") || "(no profiles)" : entries.map((e) => `${e.key}=${e.value}`).join("\n");
1795
+ return { exitCode: ExitCode.OK, result: ok({ entries, profiles, humanHint: hint }) };
1675
1796
  }
1676
1797
  async function runConfigPath(input) {
1677
1798
  const filePath = configPath(input.home);
@@ -1855,6 +1976,34 @@ function checkPluginVersionDrift(home, currentVersion) {
1855
1976
  return check("pass", "plugin_version_drift", "Plugin/CLI version", "Could not read plugin cache");
1856
1977
  }
1857
1978
  }
1979
+ async function checkProfiles(home) {
1980
+ const map = await parseDotenvFile(configPath(home));
1981
+ const profiles = [];
1982
+ for (const key of Object.keys(map)) {
1983
+ if (key.startsWith("WIKI_") && key.endsWith("_PATH") && key !== "WIKI_PATH") {
1984
+ const name = key.slice(5, -5).toLowerCase().replace(/_/g, "-");
1985
+ profiles.push(name);
1986
+ }
1987
+ }
1988
+ if (profiles.length === 0) {
1989
+ return check("pass", "wiki_profiles", "Wiki profiles", "No named profiles configured");
1990
+ }
1991
+ const defaultProfile = map["WIKI_DEFAULT"] ?? "(none)";
1992
+ return check(
1993
+ "pass",
1994
+ "wiki_profiles",
1995
+ "Wiki profiles",
1996
+ `${profiles.length} profile(s): ${profiles.join(", ")}; default: ${defaultProfile}`
1997
+ );
1998
+ }
1999
+ async function checkProjectLocalOverride(cwd) {
2000
+ const dir = cwd ?? process.cwd();
2001
+ const envPath = join15(dir, ".skillwiki", ".env");
2002
+ if (existsSync3(envPath)) {
2003
+ return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
2004
+ }
2005
+ return check("pass", "project_local", "Project-local config", "None");
2006
+ }
1858
2007
  function findSkillMd(dir) {
1859
2008
  const results = [];
1860
2009
  let entries;
@@ -1877,6 +2026,8 @@ async function runDoctor(input) {
1877
2026
  checks.push(checkNodeVersion());
1878
2027
  checks.push(checkCliOnPath(input.argv));
1879
2028
  checks.push(await checkConfigFile(input.home));
2029
+ checks.push(await checkProfiles(input.home));
2030
+ checks.push(await checkProjectLocalOverride(input.cwd));
1880
2031
  const resolved = await resolveRuntimePath({ flag: void 0, envValue: input.envValue, home: input.home });
1881
2032
  if (resolved.ok) {
1882
2033
  checks.push(check("pass", "wiki_path_set", "WIKI_PATH configured", `Resolved via ${resolved.data.source}: ${resolved.data.path}`));
@@ -2243,19 +2394,20 @@ function emit(r) {
2243
2394
  program.command("hash <file>").action(async (file) => emit(await runHash({ file })));
2244
2395
  program.command("fetch-guard <url>").action(async (url) => emit(await runFetchGuard({ url })));
2245
2396
  program.command("validate <file>").action(async (file) => emit(await runValidate({ file })));
2246
- program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path", ".skillwiki/graph.json").action(async (vault, opts) => emit(await runGraphBuild({ vault, out: opts.out })));
2397
+ program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path", ".skillwiki/graph.json").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => emit(await runGraphBuild({ vault, out: opts.out })));
2247
2398
  program.command("overlap <vault>").action(async (vault) => emit(await runOverlap({ vault })));
2248
- program.command("orphans [vault]").action(async (vault) => emit(await runOrphans({
2399
+ program.command("orphans [vault]").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => emit(await runOrphans({
2249
2400
  vault,
2250
2401
  envValue: process.env.WIKI_PATH,
2251
- home: process.env.HOME ?? ""
2402
+ home: process.env.HOME ?? "",
2403
+ wiki: opts.wiki
2252
2404
  })));
2253
2405
  program.command("audit <file>").action(async (file) => emit(await runAudit({ file })));
2254
2406
  program.command("install").option("--target <dir>", "target install directory", `${process.env.HOME ?? ""}/.claude/skills/`).option("--dry-run", "preview only", false).option("--skills-root <dir>", "source skills directory (defaults to packaged)").action(async (opts) => {
2255
2407
  const skillsRoot = opts.skillsRoot ?? new URL("../skills/", import.meta.url).pathname;
2256
2408
  emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun }));
2257
2409
  });
2258
- program.command("path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).action(async (opts) => {
2410
+ program.command("path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--wiki <name>", "wiki profile name").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).action(async (opts) => {
2259
2411
  const initTime = !!opts.initTime;
2260
2412
  const flag = initTime ? opts.target : opts.vault;
2261
2413
  emit(await runPath({
@@ -2263,6 +2415,7 @@ program.command("path").option("--vault <dir>", "explicit vault override (runtim
2263
2415
  envValue: process.env.WIKI_PATH,
2264
2416
  home: process.env.HOME ?? "",
2265
2417
  initTime,
2418
+ wiki: opts.wiki,
2266
2419
  explain: !!opts.explain
2267
2420
  }));
2268
2421
  });
@@ -2274,7 +2427,7 @@ program.command("lang").option("--lang <code>", "explicit language override").op
2274
2427
  explain: !!opts.explain
2275
2428
  }));
2276
2429
  });
2277
- program.command("init").option("--target <dir>", "explicit target directory").requiredOption("--domain <text>", "knowledge domain seed").option("--taxonomy <csv>", "comma-separated tag list").option("--lang <code>", "output language (BCP 47 or alias)").option("--force", "override existing target / env conflict", false).option("--no-env", "skip writing ~/.skillwiki/.env").action(async (opts) => {
2430
+ program.command("init").option("--target <dir>", "explicit target directory").requiredOption("--domain <text>", "knowledge domain seed").option("--taxonomy <csv>", "comma-separated tag list").option("--lang <code>", "output language (BCP 47 or alias)").option("--force", "override existing target / env conflict", false).option("--no-env", "skip writing ~/.skillwiki/.env").option("--profile <name>", "write as named wiki profile instead of WIKI_PATH").action(async (opts) => {
2278
2431
  const templates = new URL("../templates/", import.meta.url).pathname;
2279
2432
  const taxonomy = typeof opts.taxonomy === "string" ? opts.taxonomy.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
2280
2433
  emit(await runInit({
@@ -2286,51 +2439,57 @@ program.command("init").option("--target <dir>", "explicit target directory").re
2286
2439
  taxonomy,
2287
2440
  lang: opts.lang,
2288
2441
  force: !!opts.force,
2289
- noEnv: opts.env === false
2442
+ noEnv: opts.env === false,
2443
+ profile: opts.profile
2290
2444
  }));
2291
2445
  });
2292
- async function resolveVaultArg(arg) {
2446
+ async function resolveVaultArg(arg, wiki) {
2293
2447
  if (arg) return { ok: true, vault: arg };
2294
2448
  const r = await resolveRuntimePath({
2295
2449
  flag: void 0,
2296
2450
  envValue: process.env.WIKI_PATH,
2297
- home: process.env.HOME ?? ""
2451
+ wikiEnv: process.env.WIKI,
2452
+ home: process.env.HOME ?? "",
2453
+ wiki
2298
2454
  });
2299
- if (!r.ok) return { ok: false, exitCode: 25, payload: r };
2455
+ if (!r.ok) {
2456
+ const exitCode = r.error === "UNKNOWN_WIKI_PROFILE" ? 35 : 25;
2457
+ return { ok: false, exitCode, payload: r };
2458
+ }
2300
2459
  return { ok: true, vault: r.data.path };
2301
2460
  }
2302
- program.command("links [vault]").action(async (vault) => {
2303
- const v = await resolveVaultArg(vault);
2461
+ program.command("links [vault]").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2462
+ const v = await resolveVaultArg(vault, opts.wiki);
2304
2463
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2305
2464
  else emit(await runLinks({ vault: v.vault }));
2306
2465
  });
2307
- program.command("tag-audit [vault]").action(async (vault) => {
2308
- const v = await resolveVaultArg(vault);
2466
+ program.command("tag-audit [vault]").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2467
+ const v = await resolveVaultArg(vault, opts.wiki);
2309
2468
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2310
2469
  else emit(await runTagAudit({ vault: v.vault }));
2311
2470
  });
2312
- program.command("index-check [vault]").action(async (vault) => {
2313
- const v = await resolveVaultArg(vault);
2471
+ program.command("index-check [vault]").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2472
+ const v = await resolveVaultArg(vault, opts.wiki);
2314
2473
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2315
2474
  else emit(await runIndexCheck({ vault: v.vault }));
2316
2475
  });
2317
- program.command("stale [vault]").option("--days <n>", "staleness threshold in days", (s) => parseInt(s, 10), 90).action(async (vault, opts) => {
2318
- const v = await resolveVaultArg(vault);
2476
+ program.command("stale [vault]").option("--days <n>", "staleness threshold in days", (s) => parseInt(s, 10), 90).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2477
+ const v = await resolveVaultArg(vault, opts.wiki);
2319
2478
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2320
2479
  else emit(await runStale({ vault: v.vault, days: opts.days }));
2321
2480
  });
2322
- program.command("pagesize [vault]").option("--lines <n>", "max body lines", (s) => parseInt(s, 10), 200).action(async (vault, opts) => {
2323
- const v = await resolveVaultArg(vault);
2481
+ program.command("pagesize [vault]").option("--lines <n>", "max body lines", (s) => parseInt(s, 10), 200).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2482
+ const v = await resolveVaultArg(vault, opts.wiki);
2324
2483
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2325
2484
  else emit(await runPagesize({ vault: v.vault, lines: opts.lines }));
2326
2485
  });
2327
- program.command("log-rotate [vault]").option("--threshold <n>", "entry count threshold", (s) => parseInt(s, 10), 500).option("--apply", "actually rotate", false).action(async (vault, opts) => {
2328
- const v = await resolveVaultArg(vault);
2486
+ program.command("log-rotate [vault]").option("--threshold <n>", "entry count threshold", (s) => parseInt(s, 10), 500).option("--apply", "actually rotate", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2487
+ const v = await resolveVaultArg(vault, opts.wiki);
2329
2488
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2330
2489
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }));
2331
2490
  });
2332
- program.command("lint [vault]").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).action(async (vault, opts) => {
2333
- const v = await resolveVaultArg(vault);
2491
+ program.command("lint [vault]").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2492
+ const v = await resolveVaultArg(vault, opts.wiki);
2334
2493
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2335
2494
  else emit(await runLint({
2336
2495
  vault: v.vault,
@@ -2343,31 +2502,32 @@ program.command("lint [vault]").option("--days <n>", "stale threshold", (s) => p
2343
2502
  var configCmd = program.command("config").description("manage skillwiki configuration");
2344
2503
  configCmd.command("get <key>").description("print the value of a config key").action(async (key) => emit(await runConfigGet({ key, home: process.env.HOME ?? "" })));
2345
2504
  configCmd.command("set <key> <value>").description("set a config key value").action(async (key, value) => emit(await runConfigSet({ key, value, home: process.env.HOME ?? "" })));
2346
- configCmd.command("list").description("list all config key=value pairs").action(async () => emit(await runConfigList({ home: process.env.HOME ?? "" })));
2505
+ configCmd.command("list").option("--profiles", "show wiki profiles summary", false).description("list all config key=value pairs").action(async (opts) => emit(await runConfigList({ home: process.env.HOME ?? "", profiles: !!opts.profiles })));
2347
2506
  configCmd.command("path").description("print the config file path").action(async () => emit(await runConfigPath({ home: process.env.HOME ?? "" })));
2348
2507
  program.command("doctor").description("diagnose skillwiki setup issues").action(async () => emit(await runDoctor({
2349
2508
  home: process.env.HOME ?? "",
2350
2509
  envValue: process.env.WIKI_PATH,
2351
2510
  argv: process.argv,
2352
- currentVersion: pkg.version
2511
+ currentVersion: pkg.version,
2512
+ cwd: process.cwd()
2353
2513
  })));
2354
- program.command("archive <page> [vault]").description("archive a typed-knowledge page").action(async (page, vault) => {
2355
- const v = await resolveVaultArg(vault);
2514
+ program.command("archive <page> [vault]").description("archive a typed-knowledge page").option("--wiki <name>", "wiki profile name").action(async (page, vault, opts) => {
2515
+ const v = await resolveVaultArg(vault, opts.wiki);
2356
2516
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2357
2517
  else emit(await runArchive({ vault: v.vault, page }));
2358
2518
  });
2359
- program.command("drift [vault]").description("detect content drift in raw sources").action(async (vault) => {
2360
- const v = await resolveVaultArg(vault);
2519
+ program.command("drift [vault]").description("detect content drift in raw sources").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2520
+ const v = await resolveVaultArg(vault, opts.wiki);
2361
2521
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2362
2522
  else emit(await runDrift({ vault: v.vault }));
2363
2523
  });
2364
- program.command("dedup [vault]").description("detect duplicate raw sources by sha256").action(async (vault) => {
2365
- const v = await resolveVaultArg(vault);
2524
+ program.command("dedup [vault]").description("detect duplicate raw sources by sha256").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2525
+ const v = await resolveVaultArg(vault, opts.wiki);
2366
2526
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2367
2527
  else emit(await runDedup({ vault: v.vault }));
2368
2528
  });
2369
- program.command("migrate-citations [vault]").description("migrate ^[raw/...] markers to paragraph-end citations").option("--dry-run", "preview changes without writing", false).action(async (vault, opts) => {
2370
- const v = await resolveVaultArg(vault);
2529
+ program.command("migrate-citations [vault]").description("migrate ^[raw/...] markers to paragraph-end citations").option("--dry-run", "preview changes without writing", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
2530
+ const v = await resolveVaultArg(vault, opts.wiki);
2371
2531
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
2372
2532
  else emit(await runMigrateCitations({ vault: v.vault, dryRun: !!opts.dryRun }));
2373
2533
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.2.0-beta.20",
3
+ "version": "0.2.0-beta.23",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "skillwiki": "dist/cli.js"
@@ -15,7 +15,8 @@
15
15
  "build": "tsup && rm -rf ./skills && cp -r ../skills ./skills",
16
16
  "test": "vitest run",
17
17
  "test:watch": "vitest",
18
- "typecheck": "tsc --noEmit"
18
+ "typecheck": "tsc --noEmit",
19
+ "prepublishOnly": "npm run build"
19
20
  },
20
21
  "dependencies": {
21
22
  "commander": "^12.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillwiki",
3
- "version": "0.2.0-beta.20",
3
+ "version": "0.2.0-beta.23",
4
4
  "skills": "./",
5
5
  "description": "Project-aware Karpathy-style knowledge base for Claude Code: 11 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI (8 subcommands, JSON-by-default).",
6
6
  "author": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillwiki/skills",
3
- "version": "0.2.0-beta.20",
3
+ "version": "0.2.0-beta.23",
4
4
  "private": true,
5
5
  "files": [
6
6
  "wiki-*",
@@ -88,3 +88,15 @@ Run `skillwiki doctor` to diagnose setup issues. Run `skillwiki config list` to
88
88
  For longer-running project work, use `proj-init` → `proj-work` → `proj-distill` / `proj-decide`.
89
89
 
90
90
  Maintenance: **Archive** (`wiki-archive`) superseded pages, **Drift** (`wiki-reingest`) to detect stale sources, **Adapter** (`wiki-adapter-prd`) for foreign PRD format ingestion.
91
+
92
+ ## Multi-Wiki Profiles
93
+
94
+ skillwiki supports named wiki profiles for working with multiple vaults. Set `WIKI_DEFAULT` to control which wiki all skills target by default.
95
+
96
+ **Manage profiles:**
97
+ - `skillwiki config set wiki.<name>.path <dir>` — register a profile
98
+ - `skillwiki config set default <name>` — set active profile
99
+ - `skillwiki config list --profiles` — list all profiles
100
+ - `skillwiki --wiki <name> lint` — override per-command
101
+
102
+ **Project-local override:** Place a `./skillwiki/.env` in a project root to bind that project to a specific wiki. Skills will use it automatically when running from that directory.