@1a35e1/sonar-cli 0.2.0 → 0.3.4
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 +151 -166
- package/dist/commands/{inbox/archive.js → archive.js} +2 -2
- package/dist/commands/config/data/download.js +2 -2
- package/dist/commands/config/data/sync.js +2 -2
- package/dist/commands/config/nuke.js +20 -2
- package/dist/commands/feed.js +105 -155
- package/dist/commands/index.js +172 -4
- package/dist/commands/{inbox/later.js → later.js} +2 -2
- package/dist/commands/refresh.js +41 -0
- package/dist/commands/{inbox/skip.js → skip.js} +2 -2
- package/dist/commands/status.js +128 -0
- package/dist/commands/sync/bookmarks.js +35 -0
- package/dist/commands/topics/add.js +71 -0
- package/dist/commands/topics/delete.js +42 -0
- package/dist/commands/topics/edit.js +97 -0
- package/dist/commands/topics/index.js +54 -0
- package/dist/commands/topics/suggest.js +125 -0
- package/dist/commands/topics/view.js +48 -0
- package/dist/components/AccountCard.js +1 -1
- package/dist/components/Banner.js +11 -0
- package/dist/components/InteractiveSession.js +95 -210
- package/dist/components/Spinner.js +5 -4
- package/dist/components/TopicCard.js +15 -0
- package/dist/components/TweetCard.js +76 -0
- package/dist/lib/ai.js +85 -0
- package/dist/lib/client.js +66 -39
- package/dist/lib/config.js +3 -2
- package/dist/lib/data-queries.js +1 -3
- package/dist/lib/skill.js +66 -226
- package/package.json +13 -3
- package/dist/commands/account.js +0 -75
- package/dist/commands/inbox/index.js +0 -103
- package/dist/commands/inbox/read.js +0 -41
- package/dist/commands/ingest/bookmarks.js +0 -55
- package/dist/commands/ingest/index.js +0 -5
- package/dist/commands/ingest/tweets.js +0 -55
- package/dist/commands/interests/create.js +0 -107
- package/dist/commands/interests/index.js +0 -56
- package/dist/commands/interests/match.js +0 -33
- package/dist/commands/interests/update.js +0 -153
- package/dist/commands/monitor.js +0 -93
- package/dist/components/InterestCard.js +0 -10
package/dist/lib/client.js
CHANGED
|
@@ -1,54 +1,81 @@
|
|
|
1
1
|
import { getApiUrl, getToken } from './config.js';
|
|
2
|
+
const MAX_RETRIES = Math.max(0, Number(process.env.SONAR_MAX_RETRIES) || 3);
|
|
3
|
+
function sleep(ms) {
|
|
4
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
function retryDelay(attempt) {
|
|
7
|
+
const base = Math.min(1000 * 2 ** attempt, 10_000);
|
|
8
|
+
return base + Math.random() * 500;
|
|
9
|
+
}
|
|
2
10
|
/**
|
|
3
11
|
* Execute a GraphQL request against the Sonar API.
|
|
4
12
|
*
|
|
13
|
+
* Retries transient failures (network errors, 5xx) with jittered exponential
|
|
14
|
+
* backoff. Deterministic failures (4xx, GraphQL errors) throw immediately.
|
|
15
|
+
* Control retries via SONAR_MAX_RETRIES env var (default 3, 0 to disable).
|
|
16
|
+
*
|
|
5
17
|
* A hard timeout (default 20 s) is applied via AbortController so that the
|
|
6
|
-
* process never hangs silently when the server is unresponsive.
|
|
7
|
-
* is intentionally surfaced as a distinct error so callers can give operators
|
|
8
|
-
* an actionable message (e.g. "check server health / retry").
|
|
18
|
+
* process never hangs silently when the server is unresponsive.
|
|
9
19
|
*/
|
|
10
20
|
export async function gql(query, variables = {}, flags = {}) {
|
|
11
21
|
const token = getToken();
|
|
12
22
|
const url = getApiUrl();
|
|
13
23
|
const timeoutMs = flags.timeoutMs ?? 20_000;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
|
+
let res;
|
|
28
|
+
try {
|
|
29
|
+
if (flags.debug) {
|
|
30
|
+
console.error(url, query, variables);
|
|
31
|
+
}
|
|
32
|
+
res = await fetch(url, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
signal: controller.signal,
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
Authorization: `Bearer ${token}`,
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({ query, variables }),
|
|
40
|
+
});
|
|
20
41
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
throw new Error(
|
|
35
|
-
'The server may be overloaded or unreachable. ' +
|
|
36
|
-
'Check SONAR_API_URL, your network connection, and retry.');
|
|
42
|
+
catch (err) {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
if (attempt < MAX_RETRIES) {
|
|
45
|
+
if (flags.debug)
|
|
46
|
+
console.error(`Retry ${attempt + 1}/${MAX_RETRIES} after network error`);
|
|
47
|
+
await sleep(retryDelay(attempt));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (err instanceof DOMException && err.name === 'AbortError') {
|
|
51
|
+
throw new Error(`Request timed out after ${timeoutMs / 1000}s. ` +
|
|
52
|
+
'The server may be overloaded or unreachable. ' +
|
|
53
|
+
'Check SONAR_API_URL, your network connection, and retry.');
|
|
54
|
+
}
|
|
55
|
+
throw new Error('Unable to reach server, please try again shortly.');
|
|
37
56
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
finally {
|
|
41
|
-
clearTimeout(timer);
|
|
42
|
-
}
|
|
43
|
-
if (!res.ok) {
|
|
44
|
-
if (flags.debug) {
|
|
45
|
-
console.error(JSON.stringify(await res.json(), null, 2));
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
46
59
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
60
|
+
// 5xx — transient, retry
|
|
61
|
+
if (res.status >= 500 && attempt < MAX_RETRIES) {
|
|
62
|
+
if (flags.debug)
|
|
63
|
+
console.error(`Retry ${attempt + 1}/${MAX_RETRIES} after HTTP ${res.status}`);
|
|
64
|
+
await sleep(retryDelay(attempt));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// 4xx — deterministic, throw immediately
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
if (flags.debug) {
|
|
70
|
+
console.error(JSON.stringify(await res.json(), null, 2));
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
73
|
+
}
|
|
74
|
+
const json = (await res.json());
|
|
75
|
+
if (json.errors && json.errors.length > 0) {
|
|
76
|
+
throw new Error(json.errors[0].message);
|
|
77
|
+
}
|
|
78
|
+
return json.data;
|
|
52
79
|
}
|
|
53
|
-
|
|
80
|
+
throw new Error('Unexpected retry exhaustion');
|
|
54
81
|
}
|
package/dist/lib/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { DB_PATH } from './db.js';
|
|
4
5
|
const CONFIG_DIR = join(homedir(), '.sonar');
|
|
5
6
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
6
7
|
export function readConfig() {
|
|
@@ -24,8 +25,8 @@ export function deleteConfig() {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
export function deleteDatabase() {
|
|
27
|
-
if (existsSync(
|
|
28
|
-
unlinkSync(
|
|
28
|
+
if (existsSync(DB_PATH)) {
|
|
29
|
+
unlinkSync(DB_PATH);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
export function writeConfig(config) {
|
package/dist/lib/data-queries.js
CHANGED
package/dist/lib/skill.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join, dirname } from 'node:path';
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
const SKILL_CONTENT = `---
|
|
5
5
|
name: sonar
|
|
6
|
-
description: Sonar CLI —
|
|
6
|
+
description: Sonar CLI — view and triage your feed, manage topics, trigger refresh jobs, and manage local Sonar config/data.
|
|
7
7
|
homepage: https://sonar.sh
|
|
8
8
|
user-invocable: true
|
|
9
9
|
allowed-tools: Bash
|
|
@@ -13,261 +13,101 @@ metadata: {"openclaw":{"emoji":"📡","primaryEnv":"SONAR_API_KEY","requires":{"
|
|
|
13
13
|
|
|
14
14
|
# Sonar CLI
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
All commands are invoked as: \`sonar <command> [subcommand] [flags]\`.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Account & Config
|
|
18
|
+
## Core usage
|
|
23
19
|
|
|
24
20
|
\`\`\`bash
|
|
25
|
-
#
|
|
26
|
-
sonar
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
sonar
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
sonar
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# Requires: SONAR_API_KEY
|
|
36
|
-
sonar config setup
|
|
21
|
+
# Default view (combined ranked stream from feed + inbox)
|
|
22
|
+
sonar
|
|
23
|
+
sonar --hours 24
|
|
24
|
+
sonar --days 3
|
|
25
|
+
sonar --kind default # default | bookmarks | followers | following
|
|
26
|
+
sonar --limit 50
|
|
27
|
+
sonar --render card # card | table
|
|
28
|
+
sonar --width 100
|
|
29
|
+
sonar --json
|
|
30
|
+
sonar --no-interactive
|
|
37
31
|
\`\`\`
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## Interests
|
|
42
|
-
|
|
43
|
-
Interests are named topic areas with keywords and related topics that drive suggestion matching.
|
|
33
|
+
## Topic management
|
|
44
34
|
|
|
45
35
|
\`\`\`bash
|
|
46
|
-
# List
|
|
47
|
-
sonar
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
sonar
|
|
55
|
-
|
|
56
|
-
# Generate with a specific vendor (overrides config preference)
|
|
57
|
-
sonar interests create --from-prompt "DeFi and crypto protocols" --vendor anthropic
|
|
58
|
-
|
|
59
|
-
# Update an existing interest (full replace)
|
|
60
|
-
sonar interests update --id <id> --name "New Name" --keywords "kw1,kw2"
|
|
61
|
-
|
|
62
|
-
# Add keywords to an existing interest (fetches current, merges, sends full list)
|
|
63
|
-
sonar interests update --id <id> --add-keywords "mcp,a2a,langgraph"
|
|
64
|
-
|
|
65
|
-
# Remove keywords from an existing interest
|
|
66
|
-
sonar interests update --id <id> --remove-keywords "old-term,deprecated-kw"
|
|
67
|
-
|
|
68
|
-
# Add and remove keywords in one shot
|
|
69
|
-
sonar interests update --id <id> --add-keywords "vibe-coding" --remove-keywords "cursor"
|
|
70
|
-
|
|
71
|
-
# Same flags work for related topics
|
|
72
|
-
sonar interests update --id <id> --add-topics "AI safety" --remove-topics "machine learning"
|
|
73
|
-
|
|
74
|
-
# Combine keyword/topic patching with a name change
|
|
75
|
-
sonar interests update --id <id> --name "New Name" --add-keywords "new-kw"
|
|
76
|
-
|
|
77
|
-
# Regenerate all fields from a new prompt (replaces everything)
|
|
78
|
-
sonar interests update --id <id> --from-prompt "Rust and WebAssembly tooling"
|
|
79
|
-
|
|
80
|
-
# Output raw JSON (agent-friendly)
|
|
81
|
-
sonar interests --json
|
|
36
|
+
# List topics
|
|
37
|
+
sonar topics
|
|
38
|
+
sonar topics --json
|
|
39
|
+
|
|
40
|
+
# Add/update topics
|
|
41
|
+
sonar topics add "AI agents"
|
|
42
|
+
sonar topics add "Rust systems programming" --description "..."
|
|
43
|
+
sonar topics edit --id <topic_id> --name "New Name"
|
|
44
|
+
sonar topics edit --id <topic_id> --description "Updated description"
|
|
45
|
+
sonar topics edit --id <topic_id> --json
|
|
82
46
|
\`\`\`
|
|
83
47
|
|
|
84
|
-
|
|
85
|
-
1. \`--vendor\` flag
|
|
86
|
-
2. \`SONAR_AI_VENDOR\` environment variable
|
|
87
|
-
3. \`vendor\` in \`~/.sonar/config.json\` (set via \`sonar config set vendor\`)
|
|
88
|
-
4. Defaults to \`openai\`
|
|
89
|
-
|
|
90
|
-
Required env vars: \`OPENAI_API_KEY\` (OpenAI) or \`ANTHROPIC_API_KEY\` (Anthropic)
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Feed
|
|
95
|
-
|
|
96
|
-
Scored tweet feed from your social network, filtered by interests.
|
|
48
|
+
## Pipeline and triage
|
|
97
49
|
|
|
98
50
|
\`\`\`bash
|
|
99
|
-
#
|
|
100
|
-
sonar
|
|
101
|
-
|
|
102
|
-
#
|
|
103
|
-
sonar
|
|
104
|
-
sonar
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
sonar
|
|
111
|
-
sonar feed --render table # compact table view
|
|
112
|
-
sonar feed --width 100 # card body width in columns
|
|
113
|
-
|
|
114
|
-
# Raw JSON output (agent-friendly)
|
|
115
|
-
sonar feed --json
|
|
51
|
+
# Trigger full refresh pipeline
|
|
52
|
+
sonar refresh
|
|
53
|
+
|
|
54
|
+
# Monitor account + queues
|
|
55
|
+
sonar status
|
|
56
|
+
sonar status --watch
|
|
57
|
+
sonar status --json
|
|
58
|
+
|
|
59
|
+
# Suggestion actions
|
|
60
|
+
sonar archive --id <suggestion_id>
|
|
61
|
+
sonar later --id <suggestion_id>
|
|
62
|
+
sonar skip --id <suggestion_id>
|
|
116
63
|
\`\`\`
|
|
117
64
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
## Suggestions (inbox)
|
|
65
|
+
## Config and local data
|
|
121
66
|
|
|
122
67
|
\`\`\`bash
|
|
123
|
-
#
|
|
124
|
-
sonar
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
sonar
|
|
128
|
-
sonar
|
|
129
|
-
sonar
|
|
130
|
-
sonar
|
|
131
|
-
|
|
132
|
-
#
|
|
133
|
-
sonar inbox --limit 50
|
|
134
|
-
|
|
135
|
-
# Update a suggestion's status (positional id replaced with --id flag)
|
|
136
|
-
sonar inbox read --id <id>
|
|
137
|
-
sonar inbox skip --id <id>
|
|
138
|
-
sonar inbox later --id <id>
|
|
139
|
-
sonar inbox archive --id <id>
|
|
140
|
-
|
|
141
|
-
# Raw JSON output
|
|
142
|
-
sonar inbox --json
|
|
143
|
-
\`\`\`
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Ingest
|
|
148
|
-
|
|
149
|
-
Trigger background jobs to ingest data.
|
|
150
|
-
|
|
151
|
-
\`\`\`bash
|
|
152
|
-
# Trigger specific jobs
|
|
153
|
-
sonar ingest tweets # Ingest recent tweets from social graph
|
|
154
|
-
sonar ingest bookmarks # Ingest X bookmarks (requires OAuth token)
|
|
155
|
-
sonar interests match # Match interests against ingested tweets (default: last 24h)
|
|
156
|
-
|
|
157
|
-
# Match tweet window (capped by plan: free=3d, pro=7d, enterprise=14d)
|
|
158
|
-
sonar interests match --days 1 # default
|
|
159
|
-
sonar interests match --days 3 # broader window (free plan max)
|
|
160
|
-
sonar interests match --days 7 # pro plan max
|
|
161
|
-
|
|
162
|
-
# Show current job queue counts (one-shot)
|
|
163
|
-
sonar monitor
|
|
164
|
-
|
|
165
|
-
# Live polling view of job queues
|
|
166
|
-
sonar monitor --watch
|
|
167
|
-
\`\`\`
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Local Data
|
|
172
|
-
|
|
173
|
-
Sync feed, suggestions, and interests to a local SQLite DB (\`~/.sonar/data.db\`) for offline querying.
|
|
174
|
-
|
|
175
|
-
\`\`\`bash
|
|
176
|
-
# Full download — wipes and repopulates ~/.sonar/data.db
|
|
68
|
+
# Show and setup config
|
|
69
|
+
sonar config
|
|
70
|
+
sonar config setup key=<API_KEY>
|
|
71
|
+
sonar config env
|
|
72
|
+
sonar config set vendor openai
|
|
73
|
+
sonar config set vendor anthropic
|
|
74
|
+
sonar config set feed-render card
|
|
75
|
+
sonar config set feed-width 100
|
|
76
|
+
|
|
77
|
+
# Local sqlite data
|
|
177
78
|
sonar config data download
|
|
178
|
-
|
|
179
|
-
# Incremental sync — upserts records newer than last sync
|
|
180
79
|
sonar config data sync
|
|
181
|
-
|
|
182
|
-
# Open an interactive sqlite3 REPL
|
|
80
|
+
sonar config data path
|
|
183
81
|
sonar config data sql
|
|
82
|
+
sonar config data backup [--out <path>]
|
|
83
|
+
sonar config data restore --from <backup_path> [--to <path>]
|
|
84
|
+
sonar config data verify [--path <db_path>]
|
|
184
85
|
|
|
185
|
-
#
|
|
186
|
-
sonar config
|
|
86
|
+
# Export this skill file
|
|
87
|
+
sonar config skill --install
|
|
187
88
|
\`\`\`
|
|
188
89
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
\`\`\`sql
|
|
192
|
-
-- Core tweet content (shared by feed and suggestions)
|
|
193
|
-
tweets (
|
|
194
|
-
id TEXT PRIMARY KEY, -- Sonar tweet UUID
|
|
195
|
-
xid TEXT, -- Twitter/X tweet ID
|
|
196
|
-
text TEXT,
|
|
197
|
-
created_at TEXT,
|
|
198
|
-
like_count INTEGER,
|
|
199
|
-
retweet_count INTEGER,
|
|
200
|
-
reply_count INTEGER,
|
|
201
|
-
author_username TEXT,
|
|
202
|
-
author_display_name TEXT,
|
|
203
|
-
author_followers_count INTEGER,
|
|
204
|
-
author_following_count INTEGER
|
|
205
|
-
)
|
|
90
|
+
## Other commands
|
|
206
91
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
score REAL,
|
|
211
|
-
matched_keywords TEXT, -- JSON array of strings
|
|
212
|
-
synced_at TEXT
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
-- Inbox suggestions
|
|
216
|
-
suggestions (
|
|
217
|
-
suggestion_id TEXT PRIMARY KEY,
|
|
218
|
-
tweet_id TEXT, -- FK → tweets.id
|
|
219
|
-
score REAL,
|
|
220
|
-
status TEXT, -- INBOX | READ | SKIPPED | LATER | ARCHIVED
|
|
221
|
-
relevance TEXT,
|
|
222
|
-
projects_matched TEXT, -- JSON (count of matched interests)
|
|
223
|
-
metadata TEXT, -- JSON
|
|
224
|
-
synced_at TEXT
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
-- Interests (topics/keywords that drive matching)
|
|
228
|
-
interests (
|
|
229
|
-
id TEXT PRIMARY KEY, -- nanoId
|
|
230
|
-
name TEXT,
|
|
231
|
-
description TEXT,
|
|
232
|
-
keywords TEXT, -- JSON array
|
|
233
|
-
topics TEXT, -- JSON array
|
|
234
|
-
created_at TEXT,
|
|
235
|
-
updated_at TEXT,
|
|
236
|
-
synced_at TEXT
|
|
237
|
-
)
|
|
92
|
+
\`\`\`bash
|
|
93
|
+
# Queue bookmark sync
|
|
94
|
+
sonar sync bookmarks
|
|
238
95
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
key TEXT PRIMARY KEY, -- e.g. "last_synced_at"
|
|
242
|
-
value TEXT
|
|
243
|
-
)
|
|
96
|
+
# Delete local config + local DB (requires explicit confirmation)
|
|
97
|
+
sonar config nuke --confirm
|
|
244
98
|
\`\`\`
|
|
245
99
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
## Environment Variables
|
|
100
|
+
## Environment variables
|
|
249
101
|
|
|
250
102
|
| Variable | Purpose |
|
|
251
103
|
|---|---|
|
|
252
|
-
| \`SONAR_API_KEY\` | API key for
|
|
253
|
-
| \`SONAR_API_URL\` | Backend URL (
|
|
254
|
-
| \`SONAR_AI_VENDOR\` |
|
|
104
|
+
| \`SONAR_API_KEY\` | API key for auth (overrides config file token) |
|
|
105
|
+
| \`SONAR_API_URL\` | Backend URL (defaults to production GraphQL endpoint) |
|
|
106
|
+
| \`SONAR_AI_VENDOR\` | Vendor override for AI-assisted operations (\`openai\` or \`anthropic\`) |
|
|
107
|
+
| \`SONAR_FEED_RENDER\` | Default feed renderer override |
|
|
108
|
+
| \`SONAR_FEED_WIDTH\` | Default card width override |
|
|
255
109
|
| \`OPENAI_API_KEY\` | Required when vendor is \`openai\` |
|
|
256
110
|
| \`ANTHROPIC_API_KEY\` | Required when vendor is \`anthropic\` |
|
|
257
|
-
|
|
258
|
-
---
|
|
259
|
-
|
|
260
|
-
## Config file
|
|
261
|
-
|
|
262
|
-
Stored at \`~/.sonar/config.json\`:
|
|
263
|
-
|
|
264
|
-
\`\`\`json
|
|
265
|
-
{
|
|
266
|
-
"token": "snr_...",
|
|
267
|
-
"apiUrl": "https://api.sonar.sh/graphql",
|
|
268
|
-
"vendor": "openai"
|
|
269
|
-
}
|
|
270
|
-
\`\`\`
|
|
271
111
|
`;
|
|
272
112
|
const DEFAULT_INSTALL_PATH = join(homedir(), '.claude', 'skills', 'sonar', 'SKILL.md');
|
|
273
113
|
export function writeSkillTo(dest, install) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1a35e1/sonar-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "X
|
|
3
|
+
"version": "0.3.4",
|
|
4
|
+
"description": "X social graph CLI for signal filtering and curation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"sonar": "dist/cli.js"
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=20"
|
|
15
15
|
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/1a35e1/sonar-cli"
|
|
19
|
+
},
|
|
16
20
|
"publishConfig": {
|
|
17
21
|
"access": "public"
|
|
18
22
|
},
|
|
@@ -44,6 +48,12 @@
|
|
|
44
48
|
"types": "graphql-codegen --config codegen.ts",
|
|
45
49
|
"sonar": "tsx src/cli.ts",
|
|
46
50
|
"build": "tsc",
|
|
47
|
-
"typecheck": "tsc --noEmit"
|
|
51
|
+
"typecheck": "tsc --noEmit",
|
|
52
|
+
"drift:schema:check": "node scripts/check-schema-drift.mjs",
|
|
53
|
+
"drift:surface:update": "node scripts/update-command-surface-snapshot.mjs",
|
|
54
|
+
"drift:surface:check": "node scripts/check-command-surface-snapshot.mjs",
|
|
55
|
+
"drift:docs:check": "node scripts/check-doc-command-parity.mjs",
|
|
56
|
+
"drift:data:check": "node scripts/check-data-compat.mjs",
|
|
57
|
+
"drift:check": "pnpm drift:surface:check && pnpm drift:docs:check && pnpm drift:data:check && pnpm drift:schema:check"
|
|
48
58
|
}
|
|
49
59
|
}
|
package/dist/commands/account.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import zod from 'zod';
|
|
4
|
-
import { Box, Text } from 'ink';
|
|
5
|
-
import { formatDistanceToNow } from 'date-fns';
|
|
6
|
-
import { gql } from '../lib/client.js';
|
|
7
|
-
import { Spinner } from '../components/Spinner.js';
|
|
8
|
-
import { AccountCard } from '../components/AccountCard.js';
|
|
9
|
-
export const options = zod.object({
|
|
10
|
-
json: zod.boolean().default(false).describe('Raw JSON output'),
|
|
11
|
-
debug: zod.boolean().default(false).describe('Debug mode'),
|
|
12
|
-
});
|
|
13
|
-
const QUERY = `
|
|
14
|
-
query Status {
|
|
15
|
-
me {
|
|
16
|
-
accountId
|
|
17
|
-
email
|
|
18
|
-
xHandle
|
|
19
|
-
xid
|
|
20
|
-
isPayingCustomer
|
|
21
|
-
indexingAccounts
|
|
22
|
-
indexedTweets
|
|
23
|
-
pendingEmbeddings
|
|
24
|
-
twitterIndexedAt
|
|
25
|
-
refreshedSuggestionsAt
|
|
26
|
-
}
|
|
27
|
-
suggestionCounts {
|
|
28
|
-
inbox
|
|
29
|
-
later
|
|
30
|
-
replied
|
|
31
|
-
read
|
|
32
|
-
skipped
|
|
33
|
-
archived
|
|
34
|
-
total
|
|
35
|
-
}
|
|
36
|
-
usage {
|
|
37
|
-
plan
|
|
38
|
-
interests { used limit atLimit }
|
|
39
|
-
apiKeys { used limit atLimit }
|
|
40
|
-
bookmarksEnabled
|
|
41
|
-
socialGraphDegrees
|
|
42
|
-
socialGraphMaxUsers
|
|
43
|
-
suggestionRefreshes { used limit atLimit resetsAt }
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
`;
|
|
47
|
-
export default function Account({ options: flags }) {
|
|
48
|
-
const [data, setData] = useState(null);
|
|
49
|
-
const [error, setError] = useState(null);
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
async function run() {
|
|
52
|
-
try {
|
|
53
|
-
const result = await gql(QUERY, {}, { debug: flags.debug });
|
|
54
|
-
if (flags.json) {
|
|
55
|
-
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
58
|
-
setData(result);
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
if (flags.debug) {
|
|
62
|
-
console.error(JSON.stringify(err, null, 2));
|
|
63
|
-
}
|
|
64
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
run();
|
|
68
|
-
}, []);
|
|
69
|
-
if (error)
|
|
70
|
-
return _jsxs(Text, { color: "red", children: ["Error: ", error] });
|
|
71
|
-
if (!data)
|
|
72
|
-
return _jsx(Spinner, { label: "Fetching account..." });
|
|
73
|
-
const { me, suggestionCounts, usage } = data;
|
|
74
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, children: [me ? _jsx(AccountCard, { me: me }) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "Account" }), _jsx(Text, { dimColor: true, children: "Not authenticated" })] })), usage && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "Plan" }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "plan: " }), _jsx(Text, { color: usage.plan === 'free' ? 'yellow' : 'green', children: usage.plan })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "interests: " }), _jsxs(Text, { color: usage.interests.atLimit ? 'red' : undefined, children: [usage.interests.used, usage.interests.limit !== null ? `/${usage.interests.limit}` : ''] })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "api keys: " }), _jsxs(Text, { color: usage.apiKeys.atLimit ? 'red' : undefined, children: [usage.apiKeys.used, usage.apiKeys.limit !== null ? `/${usage.apiKeys.limit}` : ''] })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "bookmarks: " }), usage.bookmarksEnabled ? _jsx(Text, { color: "green", children: "enabled" }) : _jsx(Text, { dimColor: true, children: "upgrade to unlock" })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "social graph: " }), usage.socialGraphDegrees, " degree", usage.socialGraphDegrees !== 1 ? 's' : '', usage.socialGraphMaxUsers !== null ? `, up to ${usage.socialGraphMaxUsers.toLocaleString()} users` : ', unlimited'] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "suggestion refreshes: " }), usage.suggestionRefreshes.limit !== null ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: usage.suggestionRefreshes.atLimit ? 'red' : undefined, children: [usage.suggestionRefreshes.used, "/", usage.suggestionRefreshes.limit] }), usage.suggestionRefreshes.resetsAt && (_jsxs(Text, { dimColor: true, children: [' ', "(resets ", formatDistanceToNow(new Date(usage.suggestionRefreshes.resetsAt), { addSuffix: true }), ")"] }))] })) : (_jsx(Text, { color: "green", children: "unlimited" }))] })] })), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: "cyan", children: "Suggestions" }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "inbox: " }), _jsx(Text, { color: suggestionCounts.inbox > 0 ? 'green' : undefined, children: suggestionCounts.inbox })] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "later: " }), suggestionCounts.later] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "replied: " }), suggestionCounts.replied] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "archived: " }), suggestionCounts.archived] }), _jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "total: " }), suggestionCounts.total] })] })] }));
|
|
75
|
-
}
|