@mepuka/skygent 0.2.0 → 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.
Files changed (62) hide show
  1. package/README.md +269 -31
  2. package/index.ts +18 -3
  3. package/package.json +1 -1
  4. package/src/cli/app.ts +4 -2
  5. package/src/cli/compact-output.ts +52 -0
  6. package/src/cli/config.ts +46 -4
  7. package/src/cli/doc/table-renderers.ts +29 -0
  8. package/src/cli/doc/thread.ts +2 -4
  9. package/src/cli/exit-codes.ts +2 -0
  10. package/src/cli/feed.ts +78 -61
  11. package/src/cli/filter-dsl.ts +146 -11
  12. package/src/cli/filter-errors.ts +13 -11
  13. package/src/cli/filter-help.ts +7 -0
  14. package/src/cli/filter-input.ts +3 -2
  15. package/src/cli/filter.ts +83 -5
  16. package/src/cli/graph.ts +297 -169
  17. package/src/cli/input.ts +45 -0
  18. package/src/cli/interval.ts +4 -33
  19. package/src/cli/jetstream.ts +2 -0
  20. package/src/cli/layers.ts +10 -0
  21. package/src/cli/logging.ts +8 -0
  22. package/src/cli/option-schemas.ts +22 -0
  23. package/src/cli/output-format.ts +11 -0
  24. package/src/cli/output-render.ts +14 -0
  25. package/src/cli/pagination.ts +17 -0
  26. package/src/cli/parse-errors.ts +30 -0
  27. package/src/cli/parse.ts +1 -47
  28. package/src/cli/pipe-input.ts +18 -0
  29. package/src/cli/pipe.ts +154 -0
  30. package/src/cli/post.ts +88 -66
  31. package/src/cli/query-fields.ts +13 -3
  32. package/src/cli/query.ts +354 -100
  33. package/src/cli/search.ts +93 -136
  34. package/src/cli/shared-options.ts +11 -63
  35. package/src/cli/shared.ts +1 -20
  36. package/src/cli/store-errors.ts +28 -21
  37. package/src/cli/store-tree.ts +6 -4
  38. package/src/cli/store.ts +41 -2
  39. package/src/cli/stream-merge.ts +105 -0
  40. package/src/cli/sync-factory.ts +24 -7
  41. package/src/cli/sync.ts +46 -67
  42. package/src/cli/thread-options.ts +25 -0
  43. package/src/cli/time.ts +171 -0
  44. package/src/cli/view-thread.ts +29 -32
  45. package/src/cli/watch.ts +55 -26
  46. package/src/domain/errors.ts +6 -1
  47. package/src/domain/format.ts +21 -0
  48. package/src/domain/order.ts +24 -0
  49. package/src/domain/primitives.ts +20 -3
  50. package/src/graph/relationships.ts +129 -0
  51. package/src/services/bsky-client.ts +11 -5
  52. package/src/services/jetstream-sync.ts +4 -4
  53. package/src/services/lineage-store.ts +15 -1
  54. package/src/services/shared.ts +48 -1
  55. package/src/services/store-cleaner.ts +5 -2
  56. package/src/services/store-commit.ts +60 -0
  57. package/src/services/store-manager.ts +69 -2
  58. package/src/services/store-renamer.ts +288 -0
  59. package/src/services/store-stats.ts +7 -5
  60. package/src/services/sync-engine.ts +149 -89
  61. package/src/services/sync-reporter.ts +3 -1
  62. package/src/services/sync-settings.ts +24 -0
package/src/cli/filter.ts CHANGED
@@ -4,7 +4,7 @@ import { Chunk, Clock, Effect, Option, Stream } from "effect";
4
4
  import { StoreQuery } from "../domain/events.js";
5
5
  import { RawPost } from "../domain/raw.js";
6
6
  import type { Post } from "../domain/post.js";
7
- import { StoreName } from "../domain/primitives.js";
7
+ import { PostUri, StoreName } from "../domain/primitives.js";
8
8
  import { BskyClient } from "../services/bsky-client.js";
9
9
  import { FilterCompiler } from "../services/filter-compiler.js";
10
10
  import { FilterLibrary } from "../services/filter-library.js";
@@ -24,6 +24,8 @@ import { renderTableLegacy } from "./doc/table.js";
24
24
  import { withExamples } from "./help.js";
25
25
  import { filterOption, filterJsonOption } from "./shared-options.js";
26
26
  import { jsonTableFormats, resolveOutputFormat, textJsonFormats } from "./output-format.js";
27
+ import { filterDslDescription, filterJsonDescription } from "./filter-help.js";
28
+ import { PositiveInt } from "./option-schemas.js";
27
29
 
28
30
  const filterNameArg = Args.text({ name: "name" }).pipe(
29
31
  Args.withSchema(StoreName),
@@ -35,6 +37,7 @@ const postJsonOption = Options.text("post-json").pipe(
35
37
  Options.optional
36
38
  );
37
39
  const postUriOption = Options.text("post-uri").pipe(
40
+ Options.withSchema(PostUri),
38
41
  Options.withDescription("Bluesky post URI (at://...)."),
39
42
  Options.optional
40
43
  );
@@ -42,7 +45,18 @@ const storeOption = Options.text("store").pipe(
42
45
  Options.withSchema(StoreName),
43
46
  Options.withDescription("Store name to sample for benchmarking")
44
47
  );
48
+ const storeTestOption = Options.text("store").pipe(
49
+ Options.withSchema(StoreName),
50
+ Options.withDescription("Store name to sample for filter testing"),
51
+ Options.optional
52
+ );
53
+ const testLimitOption = Options.integer("limit").pipe(
54
+ Options.withSchema(PositiveInt),
55
+ Options.withDescription("Number of posts to evaluate (default: 100)"),
56
+ Options.optional
57
+ );
45
58
  const sampleSizeOption = Options.integer("sample-size").pipe(
59
+ Options.withSchema(PositiveInt),
46
60
  Options.withDescription("Number of posts to evaluate (default: 1000)"),
47
61
  Options.optional
48
62
  );
@@ -267,17 +281,68 @@ export const filterTest = Command.make(
267
281
  filterJson: filterJsonOption,
268
282
  postJson: postJsonOption,
269
283
  postUri: postUriOption,
284
+ store: storeTestOption,
285
+ limit: testLimitOption,
270
286
  format: testFormatOption
271
287
  },
272
- ({ filter, filterJson, postJson, postUri, format }) =>
288
+ ({ filter, filterJson, postJson, postUri, store, limit, format }) =>
273
289
  Effect.gen(function* () {
274
290
  yield* requireFilterExpr(filter, filterJson);
275
291
  const expr = yield* parseFilterExpr(filter, filterJson);
276
292
  const runtime = yield* FilterRuntime;
293
+ const outputFormat = Option.getOrElse(format, () => "text" as const);
294
+ if (Option.isSome(store)) {
295
+ if (Option.isSome(postJson) || Option.isSome(postUri)) {
296
+ return yield* CliInputError.make({
297
+ message: "Use either --store or --post-json/--post-uri, not both.",
298
+ cause: { store: store.value, postJson, postUri }
299
+ });
300
+ }
301
+ const storeRef = yield* storeOptions.loadStoreRef(store.value);
302
+ const index = yield* StoreIndex;
303
+ const evaluateBatch = yield* runtime.evaluateBatch(expr);
304
+ const sampleLimit = Option.getOrElse(limit, () => 100);
305
+ const query = StoreQuery.make({ scanLimit: sampleLimit, order: "desc" });
306
+ const stream = index.query(storeRef, query);
307
+ const result = yield* stream.pipe(
308
+ Stream.grouped(50),
309
+ Stream.runFoldEffect(
310
+ { processed: 0, matched: 0 },
311
+ (state, batch) =>
312
+ evaluateBatch(batch).pipe(
313
+ Effect.map((results) => {
314
+ const processed = state.processed + Chunk.size(batch);
315
+ const matched =
316
+ state.matched +
317
+ Chunk.toReadonlyArray(results).filter(Boolean).length;
318
+ return { processed, matched };
319
+ })
320
+ )
321
+ )
322
+ );
323
+ if (outputFormat === "json") {
324
+ yield* writeJson({
325
+ store: storeRef.name,
326
+ processed: result.processed,
327
+ matched: result.matched,
328
+ limit: sampleLimit,
329
+ filter: expr,
330
+ filterText: formatFilterExpr(expr)
331
+ });
332
+ return;
333
+ }
334
+ const lines = [
335
+ `Matched: ${result.matched}/${result.processed}`,
336
+ `Store: ${storeRef.name}`,
337
+ `Filter: ${formatFilterExpr(expr)}`
338
+ ];
339
+ yield* writeText(lines.join("\n"));
340
+ return;
341
+ }
342
+
277
343
  const predicate = yield* runtime.evaluate(expr);
278
344
  const post = yield* loadPost(postJson, postUri);
279
345
  const ok = yield* predicate(post);
280
- const outputFormat = Option.getOrElse(format, () => "text" as const);
281
346
  if (outputFormat === "json") {
282
347
  yield* writeJson({
283
348
  ok,
@@ -297,8 +362,9 @@ export const filterTest = Command.make(
297
362
  })
298
363
  ).pipe(
299
364
  Command.withDescription(
300
- withExamples("Test a filter against a single post", [
301
- "skygent filter test --filter 'hashtag:#ai' --post-uri at://did:plc:example/app.bsky.feed.post/xyz"
365
+ withExamples("Test a filter against a post or store sample", [
366
+ "skygent filter test --filter 'hashtag:#ai' --post-uri at://did:plc:example/app.bsky.feed.post/xyz",
367
+ "skygent filter test --filter 'engagement:minLikes=10' --store my-store --limit 100"
302
368
  ])
303
369
  )
304
370
  );
@@ -414,8 +480,20 @@ export const filterDescribe = Command.make(
414
480
  )
415
481
  );
416
482
 
483
+ export const filterHelp = Command.make("help", {}, () =>
484
+ Effect.gen(function* () {
485
+ const body = [filterDslDescription(), "", filterJsonDescription()].join("\n");
486
+ yield* writeText(body);
487
+ })
488
+ ).pipe(
489
+ Command.withDescription(
490
+ withExamples("Show filter DSL and JSON help", ["skygent filter help"])
491
+ )
492
+ );
493
+
417
494
  export const filterCommand = Command.make("filter", {}).pipe(
418
495
  Command.withSubcommands([
496
+ filterHelp,
419
497
  filterList,
420
498
  filterShow,
421
499
  filterCreate,