@fdcn/sourcefed 0.2.9 → 0.3.1

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
@@ -32,13 +32,13 @@ import { createSourcefedMcp } from "@fdcn/sourcefed/mcp"
32
32
  OpenCode loads this package as both a server and a TUI plugin:
33
33
 
34
34
  ```json
35
- // opencode.json
36
- { "plugin": ["@fdcn/sourcefed@0.2.9"] }
35
+ // ~/.config/opencode/opencode.jsonc
36
+ { "plugins": ["@fdcn/sourcefed@0.3.1"] }
37
37
  ```
38
38
 
39
39
  ```json
40
- // tui.json
41
- { "plugin": ["@fdcn/sourcefed@0.2.9"] }
40
+ // ~/.config/opencode/cli.json
41
+ { "plugins": ["@fdcn/sourcefed@0.3.1"] }
42
42
  ```
43
43
 
44
44
  The package exposes `./server`, `./tui`, `./opencode`, `./opencode/tui`, and `./pi` entrypoints for hosts that load them directly.
@@ -11605,6 +11605,7 @@ var SourcefedDaemon = class {
11605
11605
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11606
11606
  const stopped = await this.service.stop(id);
11607
11607
  if ("error" in stopped) return { ok: false, error: stopped.error };
11608
+ await this.purgeMonitorEvents(monitor.target, id);
11608
11609
  return { ok: true, monitor: monitorView(stopped) };
11609
11610
  }
11610
11611
  async startMonitor(target, id) {
@@ -11618,6 +11619,7 @@ var SourcefedDaemon = class {
11618
11619
  const monitor = await ownedMonitor(this.service, id, target);
11619
11620
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11620
11621
  await this.service.remove(id);
11622
+ await this.purgeMonitorEvents(monitor.target, id);
11621
11623
  return { ok: true, removed: true };
11622
11624
  }
11623
11625
  async readEvents(target) {
@@ -11691,6 +11693,8 @@ var SourcefedDaemon = class {
11691
11693
  async deliver(key, target, listeners) {
11692
11694
  const queued = await this.queue.read(target);
11693
11695
  if (queued.length === 0) return;
11696
+ const enabled = new Set((await this.service.list()).filter((monitor) => monitor.enabled).map((monitor) => monitor.id));
11697
+ await this.dropQueued(target, queued.filter((event) => !enabled.has(event.monitorID)).map((event) => event.id));
11694
11698
  let receivedByListener = this.received.get(key);
11695
11699
  if (!receivedByListener) {
11696
11700
  receivedByListener = /* @__PURE__ */ new Map();
@@ -11700,7 +11704,7 @@ var SourcefedDaemon = class {
11700
11704
  for (const received of receivedByListener.values()) {
11701
11705
  for (const id of received) claimed.add(id);
11702
11706
  }
11703
- const fresh = queued.filter((event) => !claimed.has(event.id));
11707
+ const fresh = queued.filter((event) => enabled.has(event.monitorID) && !claimed.has(event.id));
11704
11708
  if (fresh.length === 0) return;
11705
11709
  for (const listener of [...listeners]) {
11706
11710
  let received = receivedByListener.get(listener);
@@ -11719,6 +11723,20 @@ var SourcefedDaemon = class {
11719
11723
  }
11720
11724
  }
11721
11725
  }
11726
+ async dropQueued(target, eventIDs) {
11727
+ if (eventIDs.length === 0) return;
11728
+ await this.queue.acknowledge(target, eventIDs);
11729
+ const receivedByListener = this.received.get(targetKey(target));
11730
+ if (!receivedByListener) return;
11731
+ const ids = new Set(eventIDs);
11732
+ for (const received of receivedByListener.values()) {
11733
+ for (const id of ids) received.delete(id);
11734
+ }
11735
+ }
11736
+ async purgeMonitorEvents(target, monitorID) {
11737
+ const queued = await this.queue.read(target);
11738
+ await this.dropQueued(target, queued.filter((event) => event.monitorID === monitorID).map((event) => event.id));
11739
+ }
11722
11740
  };
11723
11741
  function monitorView(monitor) {
11724
11742
  const definition = sourceDefinition(monitor.source);
package/dist/cli.js CHANGED
@@ -11528,6 +11528,7 @@ var SourcefedDaemon = class {
11528
11528
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11529
11529
  const stopped = await this.service.stop(id);
11530
11530
  if ("error" in stopped) return { ok: false, error: stopped.error };
11531
+ await this.purgeMonitorEvents(monitor.target, id);
11531
11532
  return { ok: true, monitor: monitorView(stopped) };
11532
11533
  }
11533
11534
  async startMonitor(target, id) {
@@ -11541,6 +11542,7 @@ var SourcefedDaemon = class {
11541
11542
  const monitor = await ownedMonitor(this.service, id, target);
11542
11543
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11543
11544
  await this.service.remove(id);
11545
+ await this.purgeMonitorEvents(monitor.target, id);
11544
11546
  return { ok: true, removed: true };
11545
11547
  }
11546
11548
  async readEvents(target) {
@@ -11614,6 +11616,8 @@ var SourcefedDaemon = class {
11614
11616
  async deliver(key, target, listeners) {
11615
11617
  const queued = await this.queue.read(target);
11616
11618
  if (queued.length === 0) return;
11619
+ const enabled = new Set((await this.service.list()).filter((monitor) => monitor.enabled).map((monitor) => monitor.id));
11620
+ await this.dropQueued(target, queued.filter((event) => !enabled.has(event.monitorID)).map((event) => event.id));
11617
11621
  let receivedByListener = this.received.get(key);
11618
11622
  if (!receivedByListener) {
11619
11623
  receivedByListener = /* @__PURE__ */ new Map();
@@ -11623,7 +11627,7 @@ var SourcefedDaemon = class {
11623
11627
  for (const received of receivedByListener.values()) {
11624
11628
  for (const id of received) claimed.add(id);
11625
11629
  }
11626
- const fresh = queued.filter((event) => !claimed.has(event.id));
11630
+ const fresh = queued.filter((event) => enabled.has(event.monitorID) && !claimed.has(event.id));
11627
11631
  if (fresh.length === 0) return;
11628
11632
  for (const listener of [...listeners]) {
11629
11633
  let received = receivedByListener.get(listener);
@@ -11642,6 +11646,20 @@ var SourcefedDaemon = class {
11642
11646
  }
11643
11647
  }
11644
11648
  }
11649
+ async dropQueued(target, eventIDs) {
11650
+ if (eventIDs.length === 0) return;
11651
+ await this.queue.acknowledge(target, eventIDs);
11652
+ const receivedByListener = this.received.get(targetKey(target));
11653
+ if (!receivedByListener) return;
11654
+ const ids = new Set(eventIDs);
11655
+ for (const received of receivedByListener.values()) {
11656
+ for (const id of ids) received.delete(id);
11657
+ }
11658
+ }
11659
+ async purgeMonitorEvents(target, monitorID) {
11660
+ const queued = await this.queue.read(target);
11661
+ await this.dropQueued(target, queued.filter((event) => event.monitorID === monitorID).map((event) => event.id));
11662
+ }
11645
11663
  };
11646
11664
  function monitorView(monitor) {
11647
11665
  const definition = sourceDefinition(monitor.source);
@@ -44166,7 +44184,9 @@ async function runMonitor(argv) {
44166
44184
 
44167
44185
  // packages/cli/src/utils/setup.ts
44168
44186
  import { execFile } from "node:child_process";
44169
- import { readFileSync } from "node:fs";
44187
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
44188
+ import os8 from "node:os";
44189
+ import path8 from "node:path";
44170
44190
  import { fileURLToPath } from "node:url";
44171
44191
  function setupSpec() {
44172
44192
  const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
@@ -44178,23 +44198,64 @@ async function setupHost(name, args, spec) {
44178
44198
  try {
44179
44199
  await execFile(name, args);
44180
44200
  console.error(`[sourcefed] ${name} configured \u2014 restart ${name} to load the plugin`);
44201
+ return true;
44181
44202
  } catch (error2) {
44182
44203
  const code = error2 && typeof error2 === "object" && "code" in error2 ? String(error2.code) : "";
44183
44204
  if (code === "ENOENT") {
44184
44205
  console.error(`[sourcefed] ${name} is not on PATH \u2014 install it manually:`);
44185
- console.error(name === "opencode" ? ` add "${spec}" to the plugin array in ~/.config/opencode/opencode.jsonc and tui.json` : ` run: pi install ${spec}`);
44186
- return;
44206
+ console.error(name === "opencode" ? ` add "${spec}" to the plugins array in ~/.config/opencode/opencode.jsonc and cli.json` : ` run: pi install ${spec}`);
44207
+ return false;
44208
+ }
44209
+ throw error2;
44210
+ }
44211
+ }
44212
+ function configureOpenCodeCliPlugin(spec) {
44213
+ const file = openCodeCliConfigPath();
44214
+ const config2 = readOpenCodeCliConfig(file);
44215
+ const plugins = config2.plugins;
44216
+ if (plugins === void 0) {
44217
+ config2.plugins = [spec];
44218
+ } else if (!Array.isArray(plugins)) {
44219
+ throw new Error(`OpenCode CLI config has a non-array plugins setting: ${file}`);
44220
+ } else if (!plugins.some((plugin) => isPluginEntry(plugin, spec))) {
44221
+ config2.plugins = [...plugins, spec];
44222
+ } else {
44223
+ return;
44224
+ }
44225
+ mkdirSync(path8.dirname(file), { recursive: true });
44226
+ writeFileSync(file, `${JSON.stringify(config2, null, 2)}
44227
+ `);
44228
+ }
44229
+ function openCodeCliConfigPath() {
44230
+ const configHome = process.env.XDG_CONFIG_HOME ?? path8.join(os8.homedir(), ".config");
44231
+ return path8.join(configHome, "opencode", "cli.json");
44232
+ }
44233
+ function readOpenCodeCliConfig(file) {
44234
+ try {
44235
+ const value = JSON.parse(readFileSync(file, "utf8"));
44236
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
44237
+ throw new Error(`OpenCode CLI config must contain a JSON object: ${file}`);
44187
44238
  }
44239
+ return value;
44240
+ } catch (error2) {
44241
+ if (error2 instanceof Error && "code" in error2 && error2.code === "ENOENT") return {};
44242
+ if (error2 instanceof SyntaxError) throw new Error(`OpenCode CLI config is not valid JSON: ${file}`);
44188
44243
  throw error2;
44189
44244
  }
44190
44245
  }
44246
+ function isPluginEntry(value, spec) {
44247
+ if (value === spec) return true;
44248
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
44249
+ return "package" in value && value.package === spec;
44250
+ }
44191
44251
 
44192
44252
  // packages/cli/src/commands/setup.ts
44193
44253
  async function runSetup(args) {
44194
44254
  const spec = setupSpec();
44195
44255
  const requested = args[0] ?? "all";
44196
44256
  if (requested === "opencode" || requested === "all") {
44197
- await setupHost("opencode", ["plugin", spec, "--global"], spec);
44257
+ const installed = await setupHost("opencode", ["plugin", "add", spec], spec);
44258
+ if (installed) configureOpenCodeCliPlugin(spec);
44198
44259
  }
44199
44260
  if (requested === "pi" || requested === "all") {
44200
44261
  await setupHost("pi", ["install", `npm:${spec}`], `npm:${spec}`);
@@ -44203,12 +44264,12 @@ async function runSetup(args) {
44203
44264
 
44204
44265
  // packages/cli/src/skills/command.ts
44205
44266
  import { readFile as readFile7, readdir as readdir2, stat as stat2 } from "node:fs/promises";
44206
- import path9 from "node:path";
44267
+ import path10 from "node:path";
44207
44268
 
44208
44269
  // packages/cli/src/skills/discovery.ts
44209
44270
  import { statSync } from "node:fs";
44210
44271
  import { readdir, readFile as readFile6, stat } from "node:fs/promises";
44211
- import path8 from "node:path";
44272
+ import path9 from "node:path";
44212
44273
  import { fileURLToPath as fileURLToPath2 } from "node:url";
44213
44274
 
44214
44275
  // packages/cli/src/skills/frontmatter.ts
@@ -44256,7 +44317,7 @@ async function skillsDirs() {
44256
44317
  }
44257
44318
  const directories = [];
44258
44319
  for (const name of SKILL_DIRS) {
44259
- const candidate = path8.join(skillRoot(), name);
44320
+ const candidate = path9.join(skillRoot(), name);
44260
44321
  try {
44261
44322
  if ((await stat(candidate)).isDirectory()) directories.push(candidate);
44262
44323
  } catch {
@@ -44268,7 +44329,7 @@ async function discoverSkills() {
44268
44329
  const skills = [];
44269
44330
  for (const dir of await skillsDirs()) {
44270
44331
  for (const entry of await readdir(dir)) {
44271
- const skillDir = path8.join(dir, entry);
44332
+ const skillDir = path9.join(dir, entry);
44272
44333
  let entryStat;
44273
44334
  try {
44274
44335
  entryStat = await stat(skillDir);
@@ -44276,7 +44337,7 @@ async function discoverSkills() {
44276
44337
  continue;
44277
44338
  }
44278
44339
  if (!entryStat.isDirectory()) continue;
44279
- const skillFile = path8.join(skillDir, "SKILL.md");
44340
+ const skillFile = path9.join(skillDir, "SKILL.md");
44280
44341
  try {
44281
44342
  const content = await readFile6(skillFile, "utf8");
44282
44343
  const parsed = parseFrontmatter(content);
@@ -44289,15 +44350,15 @@ async function discoverSkills() {
44289
44350
  return skills;
44290
44351
  }
44291
44352
  function skillRoot() {
44292
- let dir = path8.dirname(fileURLToPath2(import.meta.url));
44353
+ let dir = path9.dirname(fileURLToPath2(import.meta.url));
44293
44354
  for (let depth = 0; depth < 6; depth++) {
44294
- const marker = path8.join(dir, "data");
44355
+ const marker = path9.join(dir, "data");
44295
44356
  if (statSync(marker, { throwIfNoEntry: false })?.isDirectory()) return dir;
44296
- const parent = path8.dirname(dir);
44357
+ const parent = path9.dirname(dir);
44297
44358
  if (parent === dir) break;
44298
44359
  dir = parent;
44299
44360
  }
44300
- return path8.dirname(fileURLToPath2(import.meta.url));
44361
+ return path9.dirname(fileURLToPath2(import.meta.url));
44301
44362
  }
44302
44363
 
44303
44364
  // packages/cli/src/skills/command.ts
@@ -44344,7 +44405,7 @@ async function runGet(args) {
44344
44405
  output += `# ${skill.name}
44345
44406
 
44346
44407
  `;
44347
- output += await readFile7(path9.join(skill.dir, "SKILL.md"), "utf8");
44408
+ output += await readFile7(path10.join(skill.dir, "SKILL.md"), "utf8");
44348
44409
  output += "\n";
44349
44410
  if (full) output += await supplementaryFiles(skill.dir);
44350
44411
  }
@@ -44367,7 +44428,7 @@ async function runPath(name) {
44367
44428
  async function supplementaryFiles(skillDir) {
44368
44429
  let output = "";
44369
44430
  for (const subdirName of ["references", "templates"]) {
44370
- const subdir = path9.join(skillDir, subdirName);
44431
+ const subdir = path10.join(skillDir, subdirName);
44371
44432
  try {
44372
44433
  if (!(await stat2(subdir)).isDirectory()) continue;
44373
44434
  } catch {
@@ -44375,7 +44436,7 @@ async function supplementaryFiles(skillDir) {
44375
44436
  }
44376
44437
  const entries = (await readdir2(subdir)).sort();
44377
44438
  for (const entry of entries) {
44378
- const filePath = path9.join(subdir, entry);
44439
+ const filePath = path10.join(subdir, entry);
44379
44440
  try {
44380
44441
  if (!(await stat2(filePath)).isFile()) continue;
44381
44442
  } catch {
package/dist/daemon.js CHANGED
@@ -11630,6 +11630,7 @@ var SourcefedDaemon = class {
11630
11630
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11631
11631
  const stopped = await this.service.stop(id);
11632
11632
  if ("error" in stopped) return { ok: false, error: stopped.error };
11633
+ await this.purgeMonitorEvents(monitor.target, id);
11633
11634
  return { ok: true, monitor: monitorView(stopped) };
11634
11635
  }
11635
11636
  async startMonitor(target, id) {
@@ -11643,6 +11644,7 @@ var SourcefedDaemon = class {
11643
11644
  const monitor = await ownedMonitor(this.service, id, target);
11644
11645
  if (!monitor) return { ok: false, error: `monitor ${id} was not found for this target` };
11645
11646
  await this.service.remove(id);
11647
+ await this.purgeMonitorEvents(monitor.target, id);
11646
11648
  return { ok: true, removed: true };
11647
11649
  }
11648
11650
  async readEvents(target) {
@@ -11716,6 +11718,8 @@ var SourcefedDaemon = class {
11716
11718
  async deliver(key, target, listeners) {
11717
11719
  const queued = await this.queue.read(target);
11718
11720
  if (queued.length === 0) return;
11721
+ const enabled = new Set((await this.service.list()).filter((monitor) => monitor.enabled).map((monitor) => monitor.id));
11722
+ await this.dropQueued(target, queued.filter((event) => !enabled.has(event.monitorID)).map((event) => event.id));
11719
11723
  let receivedByListener = this.received.get(key);
11720
11724
  if (!receivedByListener) {
11721
11725
  receivedByListener = /* @__PURE__ */ new Map();
@@ -11725,7 +11729,7 @@ var SourcefedDaemon = class {
11725
11729
  for (const received of receivedByListener.values()) {
11726
11730
  for (const id of received) claimed.add(id);
11727
11731
  }
11728
- const fresh = queued.filter((event) => !claimed.has(event.id));
11732
+ const fresh = queued.filter((event) => enabled.has(event.monitorID) && !claimed.has(event.id));
11729
11733
  if (fresh.length === 0) return;
11730
11734
  for (const listener of [...listeners]) {
11731
11735
  let received = receivedByListener.get(listener);
@@ -11744,6 +11748,20 @@ var SourcefedDaemon = class {
11744
11748
  }
11745
11749
  }
11746
11750
  }
11751
+ async dropQueued(target, eventIDs) {
11752
+ if (eventIDs.length === 0) return;
11753
+ await this.queue.acknowledge(target, eventIDs);
11754
+ const receivedByListener = this.received.get(targetKey(target));
11755
+ if (!receivedByListener) return;
11756
+ const ids = new Set(eventIDs);
11757
+ for (const received of receivedByListener.values()) {
11758
+ for (const id of ids) received.delete(id);
11759
+ }
11760
+ }
11761
+ async purgeMonitorEvents(target, monitorID) {
11762
+ const queued = await this.queue.read(target);
11763
+ await this.dropQueued(target, queued.filter((event) => event.monitorID === monitorID).map((event) => event.id));
11764
+ }
11747
11765
  };
11748
11766
  function monitorView(monitor) {
11749
11767
  const definition = sourceDefinition(monitor.source);
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  decodeTarget,
3
3
  encodeTarget
4
- } from "./chunk-QHT4DCW7.js";
4
+ } from "./chunk-QPDTUNFA.js";
5
5
  import {
6
6
  __export
7
7
  } from "./chunk-MLKGABMK.js";