@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.
- package/README.md +269 -31
- package/index.ts +18 -3
- package/package.json +1 -1
- package/src/cli/app.ts +4 -2
- package/src/cli/compact-output.ts +52 -0
- package/src/cli/config.ts +46 -4
- package/src/cli/doc/table-renderers.ts +29 -0
- package/src/cli/doc/thread.ts +2 -4
- package/src/cli/exit-codes.ts +2 -0
- package/src/cli/feed.ts +78 -61
- package/src/cli/filter-dsl.ts +146 -11
- package/src/cli/filter-errors.ts +13 -11
- package/src/cli/filter-help.ts +7 -0
- package/src/cli/filter-input.ts +3 -2
- package/src/cli/filter.ts +83 -5
- package/src/cli/graph.ts +297 -169
- package/src/cli/input.ts +45 -0
- package/src/cli/interval.ts +4 -33
- package/src/cli/jetstream.ts +2 -0
- package/src/cli/layers.ts +10 -0
- package/src/cli/logging.ts +8 -0
- package/src/cli/option-schemas.ts +22 -0
- package/src/cli/output-format.ts +11 -0
- package/src/cli/output-render.ts +14 -0
- package/src/cli/pagination.ts +17 -0
- package/src/cli/parse-errors.ts +30 -0
- package/src/cli/parse.ts +1 -47
- package/src/cli/pipe-input.ts +18 -0
- package/src/cli/pipe.ts +154 -0
- package/src/cli/post.ts +88 -66
- package/src/cli/query-fields.ts +13 -3
- package/src/cli/query.ts +354 -100
- package/src/cli/search.ts +93 -136
- package/src/cli/shared-options.ts +11 -63
- package/src/cli/shared.ts +1 -20
- package/src/cli/store-errors.ts +28 -21
- package/src/cli/store-tree.ts +6 -4
- package/src/cli/store.ts +41 -2
- package/src/cli/stream-merge.ts +105 -0
- package/src/cli/sync-factory.ts +24 -7
- package/src/cli/sync.ts +46 -67
- package/src/cli/thread-options.ts +25 -0
- package/src/cli/time.ts +171 -0
- package/src/cli/view-thread.ts +29 -32
- package/src/cli/watch.ts +55 -26
- package/src/domain/errors.ts +6 -1
- package/src/domain/format.ts +21 -0
- package/src/domain/order.ts +24 -0
- package/src/domain/primitives.ts +20 -3
- package/src/graph/relationships.ts +129 -0
- package/src/services/bsky-client.ts +11 -5
- package/src/services/jetstream-sync.ts +4 -4
- package/src/services/lineage-store.ts +15 -1
- package/src/services/shared.ts +48 -1
- package/src/services/store-cleaner.ts +5 -2
- package/src/services/store-commit.ts +60 -0
- package/src/services/store-manager.ts +69 -2
- package/src/services/store-renamer.ts +288 -0
- package/src/services/store-stats.ts +7 -5
- package/src/services/sync-engine.ts +149 -89
- package/src/services/sync-reporter.ts +3 -1
- package/src/services/sync-settings.ts +24 -0
package/src/cli/graph.ts
CHANGED
|
@@ -3,30 +3,38 @@ import { Effect, Option, Stream } from "effect";
|
|
|
3
3
|
import { BskyClient } from "../services/bsky-client.js";
|
|
4
4
|
import { AppConfigService } from "../services/app-config.js";
|
|
5
5
|
import { IdentityResolver } from "../services/identity-resolver.js";
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
6
|
+
import { ProfileResolver } from "../services/profile-resolver.js";
|
|
7
|
+
import type { ListItemView, ListView } from "../domain/bsky.js";
|
|
8
|
+
import { AtUri } from "../domain/primitives.js";
|
|
9
|
+
import { actorArg, decodeActor } from "./shared-options.js";
|
|
8
10
|
import { CliInputError } from "./errors.js";
|
|
9
11
|
import { withExamples } from "./help.js";
|
|
10
12
|
import { writeJson, writeJsonStream, writeText } from "./output.js";
|
|
11
13
|
import { renderTableLegacy } from "./doc/table.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
import { renderProfileTable } from "./doc/table-renderers.js";
|
|
15
|
+
import { jsonNdjsonTableFormats } from "./output-format.js";
|
|
16
|
+
import { emitWithFormat } from "./output-render.js";
|
|
17
|
+
import { cursorOption as baseCursorOption, limitOption as baseLimitOption, parsePagination } from "./pagination.js";
|
|
18
|
+
import { CliPreferences } from "./preferences.js";
|
|
19
|
+
import { compactListItemView, compactListView, compactProfileView } from "./compact-output.js";
|
|
20
|
+
import {
|
|
21
|
+
buildRelationshipGraph,
|
|
22
|
+
relationshipEntries,
|
|
23
|
+
type RelationshipEntry,
|
|
24
|
+
type RelationshipNode
|
|
25
|
+
} from "../graph/relationships.js";
|
|
17
26
|
|
|
18
27
|
const listUriArg = Args.text({ name: "uri" }).pipe(
|
|
28
|
+
Args.withSchema(AtUri),
|
|
19
29
|
Args.withDescription("Bluesky list URI (at://...)")
|
|
20
30
|
);
|
|
21
31
|
|
|
22
|
-
const limitOption =
|
|
23
|
-
Options.withDescription("Maximum number of results")
|
|
24
|
-
Options.optional
|
|
32
|
+
const limitOption = baseLimitOption.pipe(
|
|
33
|
+
Options.withDescription("Maximum number of results")
|
|
25
34
|
);
|
|
26
35
|
|
|
27
|
-
const cursorOption =
|
|
28
|
-
Options.withDescription("Pagination cursor")
|
|
29
|
-
Options.optional
|
|
36
|
+
const cursorOption = baseCursorOption.pipe(
|
|
37
|
+
Options.withDescription("Pagination cursor")
|
|
30
38
|
);
|
|
31
39
|
|
|
32
40
|
const formatOption = Options.choice("format", jsonNdjsonTableFormats).pipe(
|
|
@@ -34,6 +42,17 @@ const formatOption = Options.choice("format", jsonNdjsonTableFormats).pipe(
|
|
|
34
42
|
Options.optional
|
|
35
43
|
);
|
|
36
44
|
|
|
45
|
+
const ensureSupportedFormat = (
|
|
46
|
+
format: Option.Option<typeof jsonNdjsonTableFormats[number]>,
|
|
47
|
+
configFormat: string
|
|
48
|
+
) =>
|
|
49
|
+
Option.isNone(format) && configFormat === "markdown"
|
|
50
|
+
? CliInputError.make({
|
|
51
|
+
message: 'Output format "markdown" is not supported for graph commands. Use --format json|ndjson|table.',
|
|
52
|
+
cause: { format: configFormat }
|
|
53
|
+
})
|
|
54
|
+
: Effect.void;
|
|
55
|
+
|
|
37
56
|
const purposeOption = Options.choice("purpose", ["modlist", "curatelist"]).pipe(
|
|
38
57
|
Options.withDescription("List purpose filter"),
|
|
39
58
|
Options.optional
|
|
@@ -42,19 +61,9 @@ const purposeOption = Options.choice("purpose", ["modlist", "curatelist"]).pipe(
|
|
|
42
61
|
const othersOption = Options.text("others").pipe(
|
|
43
62
|
Options.withDescription("Comma-separated list of actors to compare" )
|
|
44
63
|
);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
cursor: string | undefined
|
|
49
|
-
) => {
|
|
50
|
-
const rows = actors.map((actor) => [
|
|
51
|
-
actor.handle,
|
|
52
|
-
actor.displayName ?? "",
|
|
53
|
-
actor.did
|
|
54
|
-
]);
|
|
55
|
-
const table = renderTableLegacy(["HANDLE", "DISPLAY NAME", "DID"], rows);
|
|
56
|
-
return cursor ? `${table}\n\nCursor: ${cursor}` : table;
|
|
57
|
-
};
|
|
64
|
+
const rawOption = Options.boolean("raw").pipe(
|
|
65
|
+
Options.withDescription("Output raw relationship data (no wrapper fields)")
|
|
66
|
+
);
|
|
58
67
|
|
|
59
68
|
const renderListTable = (
|
|
60
69
|
lists: ReadonlyArray<ListView>,
|
|
@@ -71,35 +80,33 @@ const renderListTable = (
|
|
|
71
80
|
return cursor ? `${table}\n\nCursor: ${cursor}` : table;
|
|
72
81
|
};
|
|
73
82
|
|
|
74
|
-
const renderRelationshipsTable = (
|
|
75
|
-
const rows =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
did: string;
|
|
81
|
-
following?: string;
|
|
82
|
-
followedBy?: string;
|
|
83
|
-
blocking?: string;
|
|
84
|
-
blockedBy?: string;
|
|
85
|
-
blockingByList?: string;
|
|
86
|
-
blockedByList?: string;
|
|
87
|
-
};
|
|
83
|
+
const renderRelationshipsTable = (entries: ReadonlyArray<RelationshipEntry>) => {
|
|
84
|
+
const rows = entries.map((entry) => {
|
|
85
|
+
const otherInputs = entry.other.inputs.join(", ");
|
|
86
|
+
const handle = entry.other.handle ?? "";
|
|
87
|
+
const did = entry.other.did ?? (entry.other.notFound ? "not-found" : "");
|
|
88
|
+
const rel = entry.relationship;
|
|
88
89
|
return [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
otherInputs,
|
|
91
|
+
handle,
|
|
92
|
+
did,
|
|
93
|
+
rel.following ? "yes" : "",
|
|
94
|
+
rel.followedBy ? "yes" : "",
|
|
95
|
+
rel.mutual ? "yes" : "",
|
|
96
|
+
rel.blocking ? "yes" : "",
|
|
97
|
+
rel.blockedBy ? "yes" : "",
|
|
98
|
+
rel.blockingByList ? "yes" : "",
|
|
99
|
+
rel.blockedByList ? "yes" : ""
|
|
96
100
|
];
|
|
97
101
|
});
|
|
98
102
|
return renderTableLegacy(
|
|
99
103
|
[
|
|
104
|
+
"INPUTS",
|
|
105
|
+
"HANDLE",
|
|
100
106
|
"DID",
|
|
101
107
|
"FOLLOWING",
|
|
102
108
|
"FOLLOWED BY",
|
|
109
|
+
"MUTUAL",
|
|
103
110
|
"BLOCKING",
|
|
104
111
|
"BLOCKED BY",
|
|
105
112
|
"BLOCK BY LIST",
|
|
@@ -125,29 +132,38 @@ const followersCommand = Command.make(
|
|
|
125
132
|
({ actor, limit, cursor, format }) =>
|
|
126
133
|
Effect.gen(function* () {
|
|
127
134
|
const appConfig = yield* AppConfigService;
|
|
135
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
136
|
+
const preferences = yield* CliPreferences;
|
|
128
137
|
const client = yield* BskyClient;
|
|
129
|
-
const
|
|
130
|
-
const parsedLimit = yield* parseLimit(limit);
|
|
138
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
131
139
|
const options = {
|
|
132
|
-
...(
|
|
133
|
-
...(
|
|
140
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
141
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {})
|
|
134
142
|
};
|
|
135
|
-
const result = yield* client.getFollowers(
|
|
136
|
-
const
|
|
143
|
+
const result = yield* client.getFollowers(actor, options);
|
|
144
|
+
const subject = preferences.compact
|
|
145
|
+
? compactProfileView(result.subject)
|
|
146
|
+
: result.subject;
|
|
147
|
+
const followers = preferences.compact
|
|
148
|
+
? result.followers.map(compactProfileView)
|
|
149
|
+
: result.followers;
|
|
150
|
+
const payload = result.cursor
|
|
151
|
+
? { subject, followers, cursor: result.cursor }
|
|
152
|
+
: { subject, followers };
|
|
153
|
+
const followersStream = Stream.fromIterable(
|
|
154
|
+
followers as ReadonlyArray<unknown>
|
|
155
|
+
);
|
|
156
|
+
yield* emitWithFormat(
|
|
137
157
|
format,
|
|
138
158
|
appConfig.outputFormat,
|
|
139
159
|
jsonNdjsonTableFormats,
|
|
140
|
-
"json"
|
|
160
|
+
"json",
|
|
161
|
+
{
|
|
162
|
+
json: writeJson(payload),
|
|
163
|
+
ndjson: writeJsonStream(followersStream),
|
|
164
|
+
table: writeText(renderProfileTable(result.followers, result.cursor))
|
|
165
|
+
}
|
|
141
166
|
);
|
|
142
|
-
if (outputFormat === "ndjson") {
|
|
143
|
-
yield* writeJsonStream(Stream.fromIterable(result.followers));
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (outputFormat === "table") {
|
|
147
|
-
yield* writeText(renderProfileTable(result.followers, result.cursor));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
yield* writeJson(result);
|
|
151
167
|
})
|
|
152
168
|
).pipe(
|
|
153
169
|
Command.withDescription(
|
|
@@ -164,29 +180,38 @@ const followsCommand = Command.make(
|
|
|
164
180
|
({ actor, limit, cursor, format }) =>
|
|
165
181
|
Effect.gen(function* () {
|
|
166
182
|
const appConfig = yield* AppConfigService;
|
|
183
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
184
|
+
const preferences = yield* CliPreferences;
|
|
167
185
|
const client = yield* BskyClient;
|
|
168
|
-
const
|
|
169
|
-
const parsedLimit = yield* parseLimit(limit);
|
|
186
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
170
187
|
const options = {
|
|
171
|
-
...(
|
|
172
|
-
...(
|
|
188
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
189
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {})
|
|
173
190
|
};
|
|
174
|
-
const result = yield* client.getFollows(
|
|
175
|
-
const
|
|
191
|
+
const result = yield* client.getFollows(actor, options);
|
|
192
|
+
const subject = preferences.compact
|
|
193
|
+
? compactProfileView(result.subject)
|
|
194
|
+
: result.subject;
|
|
195
|
+
const follows = preferences.compact
|
|
196
|
+
? result.follows.map(compactProfileView)
|
|
197
|
+
: result.follows;
|
|
198
|
+
const payload = result.cursor
|
|
199
|
+
? { subject, follows, cursor: result.cursor }
|
|
200
|
+
: { subject, follows };
|
|
201
|
+
const followsStream = Stream.fromIterable(
|
|
202
|
+
follows as ReadonlyArray<unknown>
|
|
203
|
+
);
|
|
204
|
+
yield* emitWithFormat(
|
|
176
205
|
format,
|
|
177
206
|
appConfig.outputFormat,
|
|
178
207
|
jsonNdjsonTableFormats,
|
|
179
|
-
"json"
|
|
208
|
+
"json",
|
|
209
|
+
{
|
|
210
|
+
json: writeJson(payload),
|
|
211
|
+
ndjson: writeJsonStream(followsStream),
|
|
212
|
+
table: writeText(renderProfileTable(result.follows, result.cursor))
|
|
213
|
+
}
|
|
180
214
|
);
|
|
181
|
-
if (outputFormat === "ndjson") {
|
|
182
|
-
yield* writeJsonStream(Stream.fromIterable(result.follows));
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (outputFormat === "table") {
|
|
186
|
-
yield* writeText(renderProfileTable(result.follows, result.cursor));
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
yield* writeJson(result);
|
|
190
215
|
})
|
|
191
216
|
).pipe(
|
|
192
217
|
Command.withDescription(
|
|
@@ -203,29 +228,38 @@ const knownFollowersCommand = Command.make(
|
|
|
203
228
|
({ actor, limit, cursor, format }) =>
|
|
204
229
|
Effect.gen(function* () {
|
|
205
230
|
const appConfig = yield* AppConfigService;
|
|
231
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
232
|
+
const preferences = yield* CliPreferences;
|
|
206
233
|
const client = yield* BskyClient;
|
|
207
|
-
const
|
|
208
|
-
const parsedLimit = yield* parseLimit(limit);
|
|
234
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
209
235
|
const options = {
|
|
210
|
-
...(
|
|
211
|
-
...(
|
|
236
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
237
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {})
|
|
212
238
|
};
|
|
213
|
-
const result = yield* client.getKnownFollowers(
|
|
214
|
-
const
|
|
239
|
+
const result = yield* client.getKnownFollowers(actor, options);
|
|
240
|
+
const subject = preferences.compact
|
|
241
|
+
? compactProfileView(result.subject)
|
|
242
|
+
: result.subject;
|
|
243
|
+
const followers = preferences.compact
|
|
244
|
+
? result.followers.map(compactProfileView)
|
|
245
|
+
: result.followers;
|
|
246
|
+
const payload = result.cursor
|
|
247
|
+
? { subject, followers, cursor: result.cursor }
|
|
248
|
+
: { subject, followers };
|
|
249
|
+
const followersStream = Stream.fromIterable(
|
|
250
|
+
followers as ReadonlyArray<unknown>
|
|
251
|
+
);
|
|
252
|
+
yield* emitWithFormat(
|
|
215
253
|
format,
|
|
216
254
|
appConfig.outputFormat,
|
|
217
255
|
jsonNdjsonTableFormats,
|
|
218
|
-
"json"
|
|
256
|
+
"json",
|
|
257
|
+
{
|
|
258
|
+
json: writeJson(payload),
|
|
259
|
+
ndjson: writeJsonStream(followersStream),
|
|
260
|
+
table: writeText(renderProfileTable(result.followers, result.cursor))
|
|
261
|
+
}
|
|
219
262
|
);
|
|
220
|
-
if (outputFormat === "ndjson") {
|
|
221
|
-
yield* writeJsonStream(Stream.fromIterable(result.followers));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
if (outputFormat === "table") {
|
|
225
|
-
yield* writeText(renderProfileTable(result.followers, result.cursor));
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
yield* writeJson(result);
|
|
229
263
|
})
|
|
230
264
|
).pipe(
|
|
231
265
|
Command.withDescription(
|
|
@@ -237,12 +271,14 @@ const knownFollowersCommand = Command.make(
|
|
|
237
271
|
|
|
238
272
|
const relationshipsCommand = Command.make(
|
|
239
273
|
"relationships",
|
|
240
|
-
{ actor: actorArg, others: othersOption, format: formatOption },
|
|
241
|
-
({ actor, others, format }) =>
|
|
274
|
+
{ actor: actorArg, others: othersOption, format: formatOption, raw: rawOption },
|
|
275
|
+
({ actor, others, format, raw }) =>
|
|
242
276
|
Effect.gen(function* () {
|
|
243
277
|
const appConfig = yield* AppConfigService;
|
|
278
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
244
279
|
const client = yield* BskyClient;
|
|
245
280
|
const identities = yield* IdentityResolver;
|
|
281
|
+
const profiles = yield* ProfileResolver;
|
|
246
282
|
const resolveDid = (value: string) =>
|
|
247
283
|
Effect.gen(function* () {
|
|
248
284
|
const decoded = yield* decodeActor(value);
|
|
@@ -251,7 +287,9 @@ const relationshipsCommand = Command.make(
|
|
|
251
287
|
? actorValue
|
|
252
288
|
: yield* identities.resolveDid(actorValue);
|
|
253
289
|
});
|
|
254
|
-
const resolvedActor =
|
|
290
|
+
const resolvedActor = actor.startsWith("did:")
|
|
291
|
+
? actor
|
|
292
|
+
: yield* identities.resolveDid(actor);
|
|
255
293
|
const parsedOthers = others
|
|
256
294
|
.split(",")
|
|
257
295
|
.map((item) => item.trim())
|
|
@@ -274,22 +312,84 @@ const relationshipsCommand = Command.make(
|
|
|
274
312
|
(value) => resolveDid(value),
|
|
275
313
|
{ concurrency: "unbounded" }
|
|
276
314
|
);
|
|
315
|
+
const didToInputs = new Map<string, Array<string>>();
|
|
316
|
+
resolvedOthers.forEach((did, index) => {
|
|
317
|
+
const input = uniqueOthers[index];
|
|
318
|
+
if (input) {
|
|
319
|
+
const existing = didToInputs.get(did);
|
|
320
|
+
if (existing) {
|
|
321
|
+
existing.push(input);
|
|
322
|
+
} else {
|
|
323
|
+
didToInputs.set(did, [input]);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
277
327
|
const result = yield* client.getRelationships(resolvedActor, resolvedOthers);
|
|
278
|
-
const
|
|
328
|
+
const handleFromInputs = (inputs: ReadonlyArray<string>) =>
|
|
329
|
+
inputs.find((input) => !input.startsWith("did:"));
|
|
330
|
+
const inputsForActor = [actor];
|
|
331
|
+
const didsNeedingHandle = [resolvedActor, ...resolvedOthers].filter((did) => {
|
|
332
|
+
const inputs = did === resolvedActor ? inputsForActor : didToInputs.get(did) ?? [];
|
|
333
|
+
return handleFromInputs(inputs) === undefined;
|
|
334
|
+
});
|
|
335
|
+
const handles = yield* Effect.forEach(
|
|
336
|
+
didsNeedingHandle,
|
|
337
|
+
(did) =>
|
|
338
|
+
profiles.handleForDid(did).pipe(
|
|
339
|
+
Effect.either,
|
|
340
|
+
Effect.map((result) => [did, result] as const)
|
|
341
|
+
),
|
|
342
|
+
{ concurrency: "unbounded" }
|
|
343
|
+
);
|
|
344
|
+
const handleMap = new Map<string, string>();
|
|
345
|
+
for (const [did, result] of handles) {
|
|
346
|
+
if (result._tag === "Right") {
|
|
347
|
+
handleMap.set(did, String(result.right));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const buildNode = (
|
|
351
|
+
did: string,
|
|
352
|
+
inputs: ReadonlyArray<string>,
|
|
353
|
+
handle?: string
|
|
354
|
+
): RelationshipNode => ({
|
|
355
|
+
did,
|
|
356
|
+
inputs,
|
|
357
|
+
...(handle ? { handle } : {})
|
|
358
|
+
});
|
|
359
|
+
const actorHandle = handleFromInputs(inputsForActor) ?? handleMap.get(resolvedActor);
|
|
360
|
+
const nodesByKey = new Map<string, RelationshipNode>();
|
|
361
|
+
nodesByKey.set(resolvedActor, buildNode(resolvedActor, inputsForActor, actorHandle));
|
|
362
|
+
for (const [did, inputs] of didToInputs.entries()) {
|
|
363
|
+
const handle = handleFromInputs(inputs) ?? handleMap.get(did);
|
|
364
|
+
nodesByKey.set(did, buildNode(did, inputs, handle));
|
|
365
|
+
}
|
|
366
|
+
const graphResult = buildRelationshipGraph(
|
|
367
|
+
resolvedActor,
|
|
368
|
+
nodesByKey,
|
|
369
|
+
result.relationships
|
|
370
|
+
);
|
|
371
|
+
const entries = relationshipEntries(graphResult.graph);
|
|
372
|
+
yield* emitWithFormat(
|
|
279
373
|
format,
|
|
280
374
|
appConfig.outputFormat,
|
|
281
375
|
jsonNdjsonTableFormats,
|
|
282
|
-
"json"
|
|
376
|
+
"json",
|
|
377
|
+
{
|
|
378
|
+
json: raw
|
|
379
|
+
? writeJson(result)
|
|
380
|
+
: writeJson({
|
|
381
|
+
actor: nodesByKey.get(resolvedActor) ?? {
|
|
382
|
+
did: resolvedActor,
|
|
383
|
+
inputs: [actor]
|
|
384
|
+
},
|
|
385
|
+
relationships: entries
|
|
386
|
+
}),
|
|
387
|
+
ndjson: raw
|
|
388
|
+
? writeJsonStream(Stream.fromIterable(result.relationships))
|
|
389
|
+
: writeJsonStream(Stream.fromIterable(entries)),
|
|
390
|
+
table: writeText(renderRelationshipsTable(entries))
|
|
391
|
+
}
|
|
283
392
|
);
|
|
284
|
-
if (outputFormat === "ndjson") {
|
|
285
|
-
yield* writeJsonStream(Stream.fromIterable(result.relationships));
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (outputFormat === "table") {
|
|
289
|
-
yield* writeText(renderRelationshipsTable(result.relationships));
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
yield* writeJson(result);
|
|
293
393
|
})
|
|
294
394
|
).pipe(
|
|
295
395
|
Command.withDescription(
|
|
@@ -305,30 +405,34 @@ const listsCommand = Command.make(
|
|
|
305
405
|
({ actor, limit, cursor, purpose, format }) =>
|
|
306
406
|
Effect.gen(function* () {
|
|
307
407
|
const appConfig = yield* AppConfigService;
|
|
408
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
409
|
+
const preferences = yield* CliPreferences;
|
|
308
410
|
const client = yield* BskyClient;
|
|
309
|
-
const
|
|
310
|
-
const parsedLimit = yield* parseLimit(limit);
|
|
411
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
311
412
|
const options = {
|
|
312
|
-
...(
|
|
313
|
-
...(
|
|
413
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
414
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {}),
|
|
314
415
|
...(Option.isSome(purpose) ? { purposes: [purpose.value] } : {})
|
|
315
416
|
};
|
|
316
|
-
const result = yield* client.getLists(
|
|
317
|
-
const
|
|
417
|
+
const result = yield* client.getLists(actor, options);
|
|
418
|
+
const lists = preferences.compact
|
|
419
|
+
? result.lists.map(compactListView)
|
|
420
|
+
: result.lists;
|
|
421
|
+
const payload = result.cursor ? { lists, cursor: result.cursor } : { lists };
|
|
422
|
+
const listsStream = Stream.fromIterable(
|
|
423
|
+
lists as ReadonlyArray<ListView | ReturnType<typeof compactListView>>
|
|
424
|
+
);
|
|
425
|
+
yield* emitWithFormat(
|
|
318
426
|
format,
|
|
319
427
|
appConfig.outputFormat,
|
|
320
428
|
jsonNdjsonTableFormats,
|
|
321
|
-
"json"
|
|
429
|
+
"json",
|
|
430
|
+
{
|
|
431
|
+
json: writeJson(payload),
|
|
432
|
+
ndjson: writeJsonStream(listsStream),
|
|
433
|
+
table: writeText(renderListTable(result.lists, result.cursor))
|
|
434
|
+
}
|
|
322
435
|
);
|
|
323
|
-
if (outputFormat === "ndjson") {
|
|
324
|
-
yield* writeJsonStream(Stream.fromIterable(result.lists));
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (outputFormat === "table") {
|
|
328
|
-
yield* writeText(renderListTable(result.lists, result.cursor));
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
yield* writeJson(result);
|
|
332
436
|
})
|
|
333
437
|
).pipe(
|
|
334
438
|
Command.withDescription(
|
|
@@ -345,30 +449,38 @@ const listCommand = Command.make(
|
|
|
345
449
|
({ uri, limit, cursor, format }) =>
|
|
346
450
|
Effect.gen(function* () {
|
|
347
451
|
const appConfig = yield* AppConfigService;
|
|
452
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
453
|
+
const preferences = yield* CliPreferences;
|
|
348
454
|
const client = yield* BskyClient;
|
|
349
|
-
const
|
|
455
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
350
456
|
const options = {
|
|
351
|
-
...(
|
|
352
|
-
...(
|
|
457
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
458
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {})
|
|
353
459
|
};
|
|
354
460
|
const result = yield* client.getList(uri, options);
|
|
355
|
-
const
|
|
461
|
+
const list = preferences.compact ? compactListView(result.list) : result.list;
|
|
462
|
+
const items = preferences.compact
|
|
463
|
+
? result.items.map(compactListItemView)
|
|
464
|
+
: result.items;
|
|
465
|
+
const payload = result.cursor
|
|
466
|
+
? { list, items, cursor: result.cursor }
|
|
467
|
+
: { list, items };
|
|
468
|
+
const itemsStream = Stream.fromIterable(
|
|
469
|
+
items as ReadonlyArray<ListItemView | ReturnType<typeof compactListItemView>>
|
|
470
|
+
);
|
|
471
|
+
const header = `${result.list.name} (${result.list.purpose}) by ${result.list.creator.handle}`;
|
|
472
|
+
const body = renderListItemsTable(result.items, result.cursor);
|
|
473
|
+
yield* emitWithFormat(
|
|
356
474
|
format,
|
|
357
475
|
appConfig.outputFormat,
|
|
358
476
|
jsonNdjsonTableFormats,
|
|
359
|
-
"json"
|
|
477
|
+
"json",
|
|
478
|
+
{
|
|
479
|
+
json: writeJson(payload),
|
|
480
|
+
ndjson: writeJsonStream(itemsStream),
|
|
481
|
+
table: writeText(`${header}\n\n${body}`)
|
|
482
|
+
}
|
|
360
483
|
);
|
|
361
|
-
if (outputFormat === "ndjson") {
|
|
362
|
-
yield* writeJsonStream(Stream.fromIterable(result.items));
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (outputFormat === "table") {
|
|
366
|
-
const header = `${result.list.name} (${result.list.purpose}) by ${result.list.creator.handle}`;
|
|
367
|
-
const body = renderListItemsTable(result.items, result.cursor);
|
|
368
|
-
yield* writeText(`${header}\n\n${body}`);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
yield* writeJson(result);
|
|
372
484
|
})
|
|
373
485
|
).pipe(
|
|
374
486
|
Command.withDescription(
|
|
@@ -384,28 +496,36 @@ const blocksCommand = Command.make(
|
|
|
384
496
|
({ limit, cursor, format }) =>
|
|
385
497
|
Effect.gen(function* () {
|
|
386
498
|
const appConfig = yield* AppConfigService;
|
|
499
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
500
|
+
const preferences = yield* CliPreferences;
|
|
387
501
|
const client = yield* BskyClient;
|
|
388
|
-
const
|
|
502
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
389
503
|
const options = {
|
|
390
|
-
...(
|
|
391
|
-
...(
|
|
504
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
505
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {})
|
|
392
506
|
};
|
|
393
507
|
const result = yield* client.getBlocks(options);
|
|
394
|
-
const
|
|
508
|
+
const blocks = preferences.compact
|
|
509
|
+
? result.blocks.map(compactProfileView)
|
|
510
|
+
: result.blocks;
|
|
511
|
+
const payload = result.cursor ? { blocks, cursor: result.cursor } : { blocks };
|
|
512
|
+
const blocksStream = Stream.fromIterable(
|
|
513
|
+
blocks as ReadonlyArray<unknown>
|
|
514
|
+
);
|
|
515
|
+
yield* emitWithFormat(
|
|
395
516
|
format,
|
|
396
517
|
appConfig.outputFormat,
|
|
397
518
|
jsonNdjsonTableFormats,
|
|
398
|
-
"json"
|
|
519
|
+
"json",
|
|
520
|
+
{
|
|
521
|
+
json: writeJson(payload),
|
|
522
|
+
ndjson:
|
|
523
|
+
blocks.length === 0
|
|
524
|
+
? writeText("[]")
|
|
525
|
+
: writeJsonStream(blocksStream),
|
|
526
|
+
table: writeText(renderProfileTable(result.blocks, result.cursor))
|
|
527
|
+
}
|
|
399
528
|
);
|
|
400
|
-
if (outputFormat === "ndjson") {
|
|
401
|
-
yield* writeJsonStream(Stream.fromIterable(result.blocks));
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
if (outputFormat === "table") {
|
|
405
|
-
yield* writeText(renderProfileTable(result.blocks, result.cursor));
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
yield* writeJson(result);
|
|
409
529
|
})
|
|
410
530
|
).pipe(
|
|
411
531
|
Command.withDescription(
|
|
@@ -421,28 +541,36 @@ const mutesCommand = Command.make(
|
|
|
421
541
|
({ limit, cursor, format }) =>
|
|
422
542
|
Effect.gen(function* () {
|
|
423
543
|
const appConfig = yield* AppConfigService;
|
|
544
|
+
yield* ensureSupportedFormat(format, appConfig.outputFormat);
|
|
545
|
+
const preferences = yield* CliPreferences;
|
|
424
546
|
const client = yield* BskyClient;
|
|
425
|
-
const
|
|
547
|
+
const { limit: limitValue, cursor: cursorValue } = parsePagination(limit, cursor);
|
|
426
548
|
const options = {
|
|
427
|
-
...(
|
|
428
|
-
...(
|
|
549
|
+
...(limitValue !== undefined ? { limit: limitValue } : {}),
|
|
550
|
+
...(cursorValue !== undefined ? { cursor: cursorValue } : {})
|
|
429
551
|
};
|
|
430
552
|
const result = yield* client.getMutes(options);
|
|
431
|
-
const
|
|
553
|
+
const mutes = preferences.compact
|
|
554
|
+
? result.mutes.map(compactProfileView)
|
|
555
|
+
: result.mutes;
|
|
556
|
+
const payload = result.cursor ? { mutes, cursor: result.cursor } : { mutes };
|
|
557
|
+
const mutesStream = Stream.fromIterable(
|
|
558
|
+
mutes as ReadonlyArray<unknown>
|
|
559
|
+
);
|
|
560
|
+
yield* emitWithFormat(
|
|
432
561
|
format,
|
|
433
562
|
appConfig.outputFormat,
|
|
434
563
|
jsonNdjsonTableFormats,
|
|
435
|
-
"json"
|
|
564
|
+
"json",
|
|
565
|
+
{
|
|
566
|
+
json: writeJson(payload),
|
|
567
|
+
ndjson:
|
|
568
|
+
mutes.length === 0
|
|
569
|
+
? writeText("[]")
|
|
570
|
+
: writeJsonStream(mutesStream),
|
|
571
|
+
table: writeText(renderProfileTable(result.mutes, result.cursor))
|
|
572
|
+
}
|
|
436
573
|
);
|
|
437
|
-
if (outputFormat === "ndjson") {
|
|
438
|
-
yield* writeJsonStream(Stream.fromIterable(result.mutes));
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (outputFormat === "table") {
|
|
442
|
-
yield* writeText(renderProfileTable(result.mutes, result.cursor));
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
yield* writeJson(result);
|
|
446
574
|
})
|
|
447
575
|
).pipe(
|
|
448
576
|
Command.withDescription(
|
package/src/cli/input.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SystemError, type PlatformError } from "@effect/platform/Error";
|
|
2
|
+
import { Context, Effect, Layer, Stream } from "effect";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
4
|
+
|
|
5
|
+
export interface CliInputService {
|
|
6
|
+
readonly lines: Stream.Stream<string, PlatformError>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const makeLines = () =>
|
|
10
|
+
Stream.unwrapScoped(
|
|
11
|
+
Effect.acquireRelease(
|
|
12
|
+
Effect.sync(() =>
|
|
13
|
+
createInterface({
|
|
14
|
+
input: process.stdin,
|
|
15
|
+
crlfDelay: Infinity
|
|
16
|
+
})
|
|
17
|
+
),
|
|
18
|
+
(rl) => Effect.sync(() => rl.close())
|
|
19
|
+
).pipe(
|
|
20
|
+
Effect.map((rl) =>
|
|
21
|
+
Stream.fromAsyncIterable(
|
|
22
|
+
rl,
|
|
23
|
+
(cause) =>
|
|
24
|
+
new SystemError({
|
|
25
|
+
module: "Stream",
|
|
26
|
+
method: "stdin",
|
|
27
|
+
reason: "Unknown",
|
|
28
|
+
cause
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export class CliInput extends Context.Tag("@skygent/CliInput")<
|
|
36
|
+
CliInput,
|
|
37
|
+
CliInputService
|
|
38
|
+
>() {
|
|
39
|
+
static readonly layer = Layer.succeed(
|
|
40
|
+
CliInput,
|
|
41
|
+
CliInput.of({
|
|
42
|
+
lines: makeLines()
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
}
|