@driftless-sh/cli 0.1.23 → 0.1.25

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
@@ -136,6 +136,8 @@ driftless context get auth-boundaries --json
136
136
  | `context search <query>` | Full-text search across topics |
137
137
  | `context anchor <slug>` | Anchor a doc to a topic |
138
138
  | `context push --files` | Match topics for given file paths |
139
+ | `context export` | Export all watchers to `.driftless/watchers/*.yaml` |
140
+ | `context import` | Import watchers from `.driftless/watchers/*.yaml` |
139
141
 
140
142
  #### Dry run
141
143
 
@@ -205,6 +207,12 @@ driftless context update sdk --gotchas "Reset token on org switch"
205
207
  # Anchor a doc
206
208
  driftless context anchor auth --doc docs/auth.md --files "src/auth/**"
207
209
 
210
+ # Export all watchers to .driftless/watchers/*.yaml
211
+ driftless context export
212
+
213
+ # Import watchers from .driftless/watchers/*.yaml
214
+ driftless context import
215
+
208
216
  # Delete a topic
209
217
  driftless context delete old-feature
210
218
  ```
@@ -308,7 +316,9 @@ driftless scan
308
316
  driftless context
309
317
  ├── CRUD topics via REST API
310
318
  ├── Full-text search (Postgres tsvector)
311
- └── Pattern resolution (glob → resolved files)
319
+ ├── Pattern resolution (glob → resolved files)
320
+ ├── export → .driftless/watchers/*.yaml (context as code)
321
+ └── import ← .driftless/watchers/*.yaml (bootstrap from repo)
312
322
  ```
313
323
 
314
324
  ## License
package/dist/index.js CHANGED
@@ -445,7 +445,7 @@ var require_util = __commonJS({
445
445
  return path;
446
446
  }
447
447
  exports2.normalize = normalize;
448
- function join2(aRoot, aPath) {
448
+ function join3(aRoot, aPath) {
449
449
  if (aRoot === "") {
450
450
  aRoot = ".";
451
451
  }
@@ -477,7 +477,7 @@ var require_util = __commonJS({
477
477
  }
478
478
  return joined;
479
479
  }
480
- exports2.join = join2;
480
+ exports2.join = join3;
481
481
  exports2.isAbsolute = function(aPath) {
482
482
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
483
483
  };
@@ -650,7 +650,7 @@ var require_util = __commonJS({
650
650
  parsed.path = parsed.path.substring(0, index + 1);
651
651
  }
652
652
  }
653
- sourceURL = join2(urlGenerate(parsed), sourceURL);
653
+ sourceURL = join3(urlGenerate(parsed), sourceURL);
654
654
  }
655
655
  return normalize(sourceURL);
656
656
  }
@@ -215072,6 +215072,7 @@ ${items.length} topic${items.length === 1 ? "" : "s"}.`);
215072
215072
  function renderContextHuman(ctx) {
215073
215073
  console.log(`\u258C ${ctx.topic}`);
215074
215074
  console.log(` ${ctx.summary}`);
215075
+ if (ctx.version !== void 0) console.log(` version: ${ctx.version}`);
215075
215076
  if (ctx.stale.is_stale) {
215076
215077
  console.log(`
215077
215078
  \u26A0 STALE: ${ctx.stale.reason || "Context may be outdated"}`);
@@ -215501,6 +215502,7 @@ async function contextCommand(args) {
215501
215502
  }
215502
215503
  const updates = { last_updated: /* @__PURE__ */ new Date() };
215503
215504
  if (fileContent) updates.file_content = fileContent;
215505
+ if (docFlag) updates.anchored_doc_path = docFlag;
215504
215506
  if (noteFlag) updates.decisions = noteFlag;
215505
215507
  if (filesFlag) updates.where_files = filesFlag.split(",").map((f) => f.trim()).filter(Boolean);
215506
215508
  if (patternFlag) updates.pattern = patternFlag;
@@ -215615,6 +215617,157 @@ Context delivered for ${files.length} file${files.length === 1 ? "" : "s"}.`);
215615
215617
  }
215616
215618
  return;
215617
215619
  }
215620
+ if (subCommand === "export") {
215621
+ const dir = flags["dir"] ?? ".driftless/watchers";
215622
+ const absDir = (0, import_node_path4.resolve)(process.cwd(), dir);
215623
+ try {
215624
+ const summaries = await api.get(`/workspaces/${workspaceSlug}/watchers`);
215625
+ (0, import_node_fs4.mkdirSync)(absDir, { recursive: true });
215626
+ for (const summary of summaries) {
215627
+ const slug = summary.topic;
215628
+ let ctx;
215629
+ try {
215630
+ ctx = await api.get(`/workspaces/${workspaceSlug}/watchers/${slug}`);
215631
+ } catch {
215632
+ console.error(` skipped ${slug} (fetch failed)`);
215633
+ continue;
215634
+ }
215635
+ const lines = [];
215636
+ const needsQuotes = (s) => /[:#\[\]{}&*!|>'"%@`,]/.test(s) || s.trim() !== s;
215637
+ const safeStr = (s) => needsQuotes(s) ? `"${s.replace(/"/g, '\\"')}"` : s;
215638
+ lines.push(`slug: ${safeStr(slug)}`);
215639
+ if (ctx.version !== void 0) lines.push(`version: ${ctx.version}`);
215640
+ if (ctx.description.what) lines.push(`what: ${safeStr(ctx.description.what)}`);
215641
+ if (ctx.description.how) lines.push(`how: ${safeStr(ctx.description.how)}`);
215642
+ if (ctx.anchors.files.length > 0) {
215643
+ lines.push(`where_files:`);
215644
+ for (const f of ctx.anchors.files) lines.push(` - ${safeStr(f)}`);
215645
+ }
215646
+ if (ctx.anchors.docs.length > 0 && ctx.anchors.docs[0].path) {
215647
+ lines.push(`anchored_doc: ${safeStr(ctx.anchors.docs[0].path)}`);
215648
+ }
215649
+ if (ctx.description.decisions) lines.push(`decisions: ${safeStr(ctx.description.decisions)}`);
215650
+ if (ctx.description.gotchas.length > 0) {
215651
+ lines.push(`gotchas: ${safeStr(ctx.description.gotchas.join(". "))}`);
215652
+ }
215653
+ if (ctx.description.ownership) lines.push(`ownership: ${safeStr(ctx.description.ownership)}`);
215654
+ const yamlContent = lines.join("\n") + "\n";
215655
+ (0, import_node_fs4.writeFileSync)((0, import_node_path4.join)(absDir, `${slug}.yaml`), yamlContent, "utf-8");
215656
+ }
215657
+ if (!isJSON) {
215658
+ console.log(`Exported ${summaries.length} watcher${summaries.length === 1 ? "" : "s"} \u2192 ${dir}/`);
215659
+ } else {
215660
+ emitJSON2({ exported: summaries.length, dir });
215661
+ }
215662
+ } catch (e) {
215663
+ console.error(`Export failed: ${formatError(e)}`);
215664
+ process.exit(1);
215665
+ }
215666
+ return;
215667
+ }
215668
+ if (subCommand === "import") {
215669
+ const dir = flags["dir"] ?? ".driftless/watchers";
215670
+ const absDir = (0, import_node_path4.resolve)(process.cwd(), dir);
215671
+ if (!(0, import_node_fs4.existsSync)(absDir)) {
215672
+ console.error(`Directory not found: ${dir}`);
215673
+ process.exit(1);
215674
+ }
215675
+ let files;
215676
+ try {
215677
+ files = (0, import_node_fs4.readdirSync)(absDir).filter((f) => f.endsWith(".yaml"));
215678
+ } catch (e) {
215679
+ console.error(`Failed to read directory: ${formatError(e)}`);
215680
+ process.exit(1);
215681
+ }
215682
+ if (files.length === 0) {
215683
+ console.log(`No .yaml files found in ${dir}/`);
215684
+ return;
215685
+ }
215686
+ let created = 0;
215687
+ let updated = 0;
215688
+ for (const file of files) {
215689
+ const filePath = (0, import_node_path4.join)(absDir, file);
215690
+ let raw;
215691
+ try {
215692
+ raw = (0, import_node_fs4.readFileSync)(filePath, "utf-8");
215693
+ } catch {
215694
+ console.error(` skipped ${file} (read error)`);
215695
+ continue;
215696
+ }
215697
+ const parsed = {};
215698
+ let lastArrayKey = null;
215699
+ for (const line of raw.split("\n")) {
215700
+ if (!line.trim() || line.trim().startsWith("#")) continue;
215701
+ if (/^\s{2,}-\s/.test(line)) {
215702
+ if (lastArrayKey) {
215703
+ const item = line.replace(/^\s{2,}-\s*/, "").trim();
215704
+ const existing = parsed[lastArrayKey];
215705
+ if (Array.isArray(existing)) {
215706
+ existing.push(item);
215707
+ } else {
215708
+ parsed[lastArrayKey] = [item];
215709
+ }
215710
+ }
215711
+ continue;
215712
+ }
215713
+ const colonIdx = line.indexOf(":");
215714
+ if (colonIdx === -1) continue;
215715
+ const key = line.slice(0, colonIdx).trim();
215716
+ let value = line.slice(colonIdx + 1).trim();
215717
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
215718
+ value = value.slice(1, -1).replace(/\\"/g, '"');
215719
+ }
215720
+ if (value === "") {
215721
+ lastArrayKey = key;
215722
+ parsed[key] = [];
215723
+ } else {
215724
+ lastArrayKey = null;
215725
+ parsed[key] = value;
215726
+ }
215727
+ }
215728
+ const slug = parsed["slug"];
215729
+ if (!slug) {
215730
+ console.error(` skipped ${file} (no slug field)`);
215731
+ continue;
215732
+ }
215733
+ const payload = {};
215734
+ if (parsed["what"]) payload.what = parsed["what"];
215735
+ if (parsed["how"]) payload.how = parsed["how"];
215736
+ if (parsed["where_files"]) payload.where_files = parsed["where_files"];
215737
+ if (parsed["anchored_doc"]) payload.anchored_doc_path = parsed["anchored_doc"];
215738
+ if (parsed["decisions"]) payload.decisions = parsed["decisions"];
215739
+ if (parsed["gotchas"]) payload.gotchas = parsed["gotchas"];
215740
+ if (parsed["ownership"]) payload.ownership = parsed["ownership"];
215741
+ let exists = false;
215742
+ try {
215743
+ await api.get(`/workspaces/${workspaceSlug}/watchers/${slug}`);
215744
+ exists = true;
215745
+ } catch {
215746
+ exists = false;
215747
+ }
215748
+ try {
215749
+ if (exists) {
215750
+ await api.patch(`/workspaces/${workspaceSlug}/watchers/${slug}`, payload);
215751
+ console.log(` \u2713 ${slug} (updated)`);
215752
+ updated++;
215753
+ } else {
215754
+ const name = slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
215755
+ await api.post(`/workspaces/${workspaceSlug}/watchers`, { name, ...payload });
215756
+ console.log(` \u2713 ${slug} (created)`);
215757
+ created++;
215758
+ }
215759
+ } catch (e) {
215760
+ console.error(` \u2717 ${slug}: ${formatError(e)}`);
215761
+ }
215762
+ }
215763
+ if (!isJSON) {
215764
+ console.log(`
215765
+ Done: ${created} created, ${updated} updated.`);
215766
+ } else {
215767
+ emitJSON2({ created, updated });
215768
+ }
215769
+ return;
215770
+ }
215618
215771
  console.log(`Usage: driftless context <list|get|add|update|delete|search|push> [args]
215619
215772
 
215620
215773
  Run 'driftless help context' for full reference.`);
@@ -216048,7 +216201,8 @@ var CONFIG_DIR = (0, import_node_path7.resolve)((0, import_node_os2.homedir)(),
216048
216201
  var CONFIG_PATH2 = (0, import_node_path7.resolve)(CONFIG_DIR, "config.json");
216049
216202
  function openBrowser(url) {
216050
216203
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
216051
- (0, import_node_child_process2.exec)(`${cmd} ${url}`);
216204
+ const child = (0, import_node_child_process2.spawn)(cmd, [url], { stdio: "ignore", detached: true });
216205
+ child.unref();
216052
216206
  }
216053
216207
  async function loginCommand(args) {
216054
216208
  const keyIndex = args.indexOf("--key");
@@ -216254,7 +216408,7 @@ function pad2(s, n) {
216254
216408
  }
216255
216409
 
216256
216410
  // src/index.ts
216257
- var VERSION = "0.1.23";
216411
+ var VERSION = "0.1.25";
216258
216412
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216259
216413
 
216260
216414
  Install: npm install -g @driftless-sh/cli