@drakulavich/ottoman 0.4.1 → 0.4.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.2] — 2026-06-14
11
+
12
+ ### Fixed
13
+ - `sofa mine` renders the per-row trust signal compactly (`trust <score>` /
14
+ `unscored`) instead of dumping the raw `trust_summary` JSON. (#28)
15
+ - `sofa search` no longer prints `of null` in the footer when the server returns
16
+ no total (search mode) — it shows just the count. `PostList.total` is now typed
17
+ `number | null`. (#29)
18
+
10
19
  ## [0.4.1] — 2026-06-14
11
20
 
12
21
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakulavich/ottoman",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Bun-native library + CLI client for Stack Overflow for Agents (SOFA)",
5
5
  "license": "MIT",
6
6
  "homepage": "https://drakulavich.github.io/ottoman/",
package/src/client.ts CHANGED
@@ -74,7 +74,7 @@ export interface PostSummary {
74
74
 
75
75
  export interface PostList {
76
76
  items: PostSummary[];
77
- total: number;
77
+ total: number | null; // null in search mode — the server doesn't compute a total there.
78
78
  page: number;
79
79
  per_page: number;
80
80
  has_next: boolean;
package/src/format.ts CHANGED
@@ -14,6 +14,15 @@ export interface MineLine {
14
14
 
15
15
  const votes = (n?: number): string => (n !== undefined ? `▲${n} ` : "");
16
16
 
17
+ /** Compact trust signal for a row: ` trust <score>` when scored, ` unscored` when
18
+ * present-but-unscored, empty when absent. Avoids dumping the raw summary JSON. */
19
+ function trustToken(summary: unknown): string {
20
+ if (summary === null || summary === undefined || typeof summary !== "object") return "";
21
+ const s = summary as { status?: unknown; score?: unknown };
22
+ if (s.status === "scored" && typeof s.score === "number") return ` trust ${s.score}`;
23
+ return " unscored";
24
+ }
25
+
17
26
  export function formatSearch(list: PostList): string {
18
27
  if (list.items.length === 0) {
19
28
  // Surface the server's steering hint (rephrase / contribute) instead of a bare miss.
@@ -22,7 +31,9 @@ export function formatSearch(list: PostList): string {
22
31
  const lines = list.items.map(
23
32
  (p) => `${p.id} [${p.content_type}] ${p.title} (${votes(p.vote_count)}💬${p.reply_count} by ${p.agent_name})`,
24
33
  );
25
- lines.push(`— page ${list.page}, showing ${list.items.length} of ${list.total}${list.has_next ? " (more pages)" : ""}`);
34
+ // Search mode can return total: null don't leak "of null"; show just the count.
35
+ const shown = list.total == null ? `showing ${list.items.length}` : `showing ${list.items.length} of ${list.total}`;
36
+ lines.push(`— page ${list.page}, ${shown}${list.has_next ? " (more pages)" : ""}`);
26
37
  return lines.join("\n");
27
38
  }
28
39
 
@@ -44,9 +55,7 @@ export function formatMine(lines: MineLine[]): string {
44
55
  return lines
45
56
  .map((p) => {
46
57
  if (p.deleted) return `<deleted> (${p.id})`;
47
- const ts = p.trust_summary !== null && p.trust_summary !== undefined
48
- ? ` trust:${JSON.stringify(p.trust_summary)}`
49
- : "";
58
+ const ts = trustToken(p.trust_summary);
50
59
  return `${p.id} [${p.content_type}] ${p.title} (${votes(p.vote_count)}💬${p.reply_count} 👁${p.view_count}${ts})`;
51
60
  })
52
61
  .join("\n");