@1a35e1/sonar-cli 0.1.0

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 (42) hide show
  1. package/README.md +422 -0
  2. package/dist/cli.js +4 -0
  3. package/dist/commands/account.js +75 -0
  4. package/dist/commands/config/data/download.js +53 -0
  5. package/dist/commands/config/data/path.js +11 -0
  6. package/dist/commands/config/data/sql.js +12 -0
  7. package/dist/commands/config/data/sync.js +85 -0
  8. package/dist/commands/config/env.js +15 -0
  9. package/dist/commands/config/index.js +12 -0
  10. package/dist/commands/config/nuke.js +19 -0
  11. package/dist/commands/config/set.js +38 -0
  12. package/dist/commands/config/setup.js +29 -0
  13. package/dist/commands/config/skill.js +15 -0
  14. package/dist/commands/feed.js +172 -0
  15. package/dist/commands/inbox/archive.js +41 -0
  16. package/dist/commands/inbox/index.js +92 -0
  17. package/dist/commands/inbox/later.js +41 -0
  18. package/dist/commands/inbox/read.js +41 -0
  19. package/dist/commands/inbox/skip.js +41 -0
  20. package/dist/commands/index.js +5 -0
  21. package/dist/commands/ingest/bookmarks.js +31 -0
  22. package/dist/commands/ingest/index.js +5 -0
  23. package/dist/commands/ingest/tweets.js +31 -0
  24. package/dist/commands/interests/create.js +94 -0
  25. package/dist/commands/interests/index.js +56 -0
  26. package/dist/commands/interests/match.js +33 -0
  27. package/dist/commands/interests/update.js +142 -0
  28. package/dist/commands/monitor.js +81 -0
  29. package/dist/components/AccountCard.js +6 -0
  30. package/dist/components/InteractiveSession.js +241 -0
  31. package/dist/components/InterestCard.js +10 -0
  32. package/dist/components/RefreshTip.js +5 -0
  33. package/dist/components/Spinner.js +14 -0
  34. package/dist/components/Table.js +23 -0
  35. package/dist/lib/ai.js +160 -0
  36. package/dist/lib/client.js +33 -0
  37. package/dist/lib/config.js +74 -0
  38. package/dist/lib/data-queries.js +61 -0
  39. package/dist/lib/db.js +73 -0
  40. package/dist/lib/skill.js +290 -0
  41. package/dist/types/sonar.js +42 -0
  42. package/package.json +47 -0
@@ -0,0 +1,290 @@
1
+ import { writeFileSync, mkdirSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const SKILL_CONTENT = `---
5
+ name: sonar
6
+ description: Sonar CLI — manage interests, suggestions, indexing jobs, and account config for the Sonar social intelligence platform. Use when the user asks about their Sonar account, wants to create/list interests, check suggestions, trigger indexing, or configure the CLI.
7
+ homepage: https://sonar.sh
8
+ user-invocable: true
9
+ allowed-tools: Bash
10
+ argument-hint: [command and options]
11
+ metadata: {"openclaw":{"emoji":"📡","primaryEnv":"SONAR_API_KEY","requires":{"bins":["sonar"],"env":["SONAR_API_KEY"]}}}
12
+ ---
13
+
14
+ # Sonar CLI
15
+
16
+ Sonar is a social intelligence platform. Use the \`sonar\` CLI to manage the user's account.
17
+
18
+ All commands are invoked as: \`sonar <command> [subcommand] [flags]\`
19
+
20
+ ---
21
+
22
+ ## Account & Config
23
+
24
+ \`\`\`bash
25
+ # Show account info, plan usage, and suggestion counts
26
+ sonar account
27
+
28
+ # Show current CLI config (API URL, vendor, token presence)
29
+ sonar config
30
+
31
+ # Set AI vendor preference for --from-prompt (saved to ~/.sonar/config.json)
32
+ sonar config set vendor openai # or: anthropic
33
+
34
+ # Initialise workspace from environment variables
35
+ # Requires: SONAR_API_KEY
36
+ sonar config setup
37
+ \`\`\`
38
+
39
+ ---
40
+
41
+ ## Interests
42
+
43
+ Interests are named topic areas with keywords and related topics that drive suggestion matching.
44
+
45
+ \`\`\`bash
46
+ # List all interests
47
+ sonar interests
48
+
49
+ # Create manually
50
+ sonar interests create --name "AI Agents" --description "LLM-based agents and tooling" \\
51
+ --keywords "agents,llm,tools,mcp" --topics "machine learning,AI safety"
52
+
53
+ # Generate fields from a natural language prompt (uses OPENAI_API_KEY or ANTHROPIC_API_KEY)
54
+ sonar interests create --from-prompt "I want to follow the Rust ecosystem and systems programming"
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
82
+ \`\`\`
83
+
84
+ **AI vendor resolution order:**
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.
97
+
98
+ \`\`\`bash
99
+ # Show feed (default: last 12h, limit 20, card layout)
100
+ sonar feed
101
+
102
+ # Time window
103
+ sonar feed --hours 24
104
+ sonar feed --days 3
105
+
106
+ # Limit results
107
+ sonar feed --limit 50
108
+
109
+ # Output layout
110
+ sonar feed --render card # default — rich card view
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
116
+ \`\`\`
117
+
118
+ ---
119
+
120
+ ## Suggestions (inbox)
121
+
122
+ \`\`\`bash
123
+ # List suggestions (default: inbox, limit 20)
124
+ sonar inbox
125
+
126
+ # Filter by status
127
+ sonar inbox --status inbox
128
+ sonar inbox --status later
129
+ sonar inbox --status replied
130
+ sonar inbox --status archived
131
+
132
+ # Change limit
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
177
+ sonar config data download
178
+
179
+ # Incremental sync — upserts records newer than last sync
180
+ sonar config data sync
181
+
182
+ # Open an interactive sqlite3 REPL
183
+ sonar config data sql
184
+
185
+ # Print path to the local DB file
186
+ sonar config data path
187
+ \`\`\`
188
+
189
+ ### Schema
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
+ )
206
+
207
+ -- Feed items (scored, keyword-matched tweets)
208
+ feed_items (
209
+ tweet_id TEXT PRIMARY KEY, -- FK → tweets.id
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
+ )
238
+
239
+ -- Internal sync state
240
+ sync_state (
241
+ key TEXT PRIMARY KEY, -- e.g. "last_synced_at"
242
+ value TEXT
243
+ )
244
+ \`\`\`
245
+
246
+ ---
247
+
248
+ ## Environment Variables
249
+
250
+ | Variable | Purpose |
251
+ |---|---|
252
+ | \`SONAR_API_KEY\` | API key for authentication (overrides config file) |
253
+ | \`SONAR_API_URL\` | Backend URL (default: \`http://localhost:8000/graphql\`) |
254
+ | \`SONAR_AI_VENDOR\` | AI vendor for \`--from-prompt\` (overrides config file) |
255
+ | \`OPENAI_API_KEY\` | Required when vendor is \`openai\` |
256
+ | \`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
+ `;
272
+ const DEFAULT_INSTALL_PATH = join(homedir(), '.claude', 'skills', 'sonar', 'SKILL.md');
273
+ export function writeSkillTo(dest, install) {
274
+ if (install || dest === '--install') {
275
+ const target = DEFAULT_INSTALL_PATH;
276
+ mkdirSync(dirname(target), { recursive: true });
277
+ writeFileSync(target, SKILL_CONTENT, 'utf8');
278
+ process.stdout.write(`SKILL.md written to ${target}\n`);
279
+ process.exit(0);
280
+ }
281
+ if (dest) {
282
+ mkdirSync(dirname(dest), { recursive: true });
283
+ writeFileSync(dest, SKILL_CONTENT, 'utf8');
284
+ process.stdout.write(`SKILL.md written to ${dest}\n`);
285
+ process.exit(0);
286
+ }
287
+ // Default: print to stdout
288
+ process.stdout.write(SKILL_CONTENT);
289
+ process.exit(0);
290
+ }
@@ -0,0 +1,42 @@
1
+ import { print } from 'graphql';
2
+ export var RelevanceLevel;
3
+ (function (RelevanceLevel) {
4
+ RelevanceLevel["High"] = "HIGH";
5
+ RelevanceLevel["Low"] = "LOW";
6
+ RelevanceLevel["Medium"] = "MEDIUM";
7
+ RelevanceLevel["None"] = "NONE";
8
+ })(RelevanceLevel || (RelevanceLevel = {}));
9
+ export var SuggestionStatus;
10
+ (function (SuggestionStatus) {
11
+ SuggestionStatus["Archived"] = "ARCHIVED";
12
+ SuggestionStatus["Inbox"] = "INBOX";
13
+ SuggestionStatus["Later"] = "LATER";
14
+ SuggestionStatus["Read"] = "READ";
15
+ SuggestionStatus["Replied"] = "REPLIED";
16
+ SuggestionStatus["Skipped"] = "SKIPPED";
17
+ })(SuggestionStatus || (SuggestionStatus = {}));
18
+ export const IndexBookmarksDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "IndexBookmarks" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "indexBookmarks" } }] } }] };
19
+ export const IndexTweetsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "IndexTweets" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "indexTweets" } }] } }] };
20
+ export const RegenerateSuggestionsDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "mutation", "name": { "kind": "Name", "value": "RegenerateSuggestions" }, "variableDefinitions": [{ "kind": "VariableDefinition", "variable": { "kind": "Variable", "name": { "kind": "Name", "value": "days" } }, "type": { "kind": "NamedType", "name": { "kind": "Name", "value": "Int" } } }], "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "regenerateSuggestions" }, "arguments": [{ "kind": "Argument", "name": { "kind": "Name", "value": "days" }, "value": { "kind": "Variable", "name": { "kind": "Name", "value": "days" } } }] }] } }] };
21
+ export const MonitorStatusDocument = { "kind": "Document", "definitions": [{ "kind": "OperationDefinition", "operation": "query", "name": { "kind": "Name", "value": "MonitorStatus" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "me" }, "selectionSet": { "kind": "SelectionSet", "selections": [{ "kind": "Field", "name": { "kind": "Name", "value": "accountId" } }, { "kind": "Field", "name": { "kind": "Name", "value": "email" } }, { "kind": "Field", "name": { "kind": "Name", "value": "xHandle" } }, { "kind": "Field", "name": { "kind": "Name", "value": "xid" } }, { "kind": "Field", "name": { "kind": "Name", "value": "isPayingCustomer" } }, { "kind": "Field", "name": { "kind": "Name", "value": "indexingAccounts" } }, { "kind": "Field", "name": { "kind": "Name", "value": "indexedTweets" } }, { "kind": "Field", "name": { "kind": "Name", "value": "pendingEmbeddings" } }, { "kind": "Field", "name": { "kind": "Name", "value": "twitterIndexedAt" } }, { "kind": "Field", "name": { "kind": "Name", "value": "refreshedSuggestionsAt" } }] } }] } }] };
22
+ const defaultWrapper = (action, _operationName, _operationType, _variables) => action();
23
+ const IndexBookmarksDocumentString = print(IndexBookmarksDocument);
24
+ const IndexTweetsDocumentString = print(IndexTweetsDocument);
25
+ const RegenerateSuggestionsDocumentString = print(RegenerateSuggestionsDocument);
26
+ const MonitorStatusDocumentString = print(MonitorStatusDocument);
27
+ export function getSdk(client, withWrapper = defaultWrapper) {
28
+ return {
29
+ IndexBookmarks(variables, requestHeaders) {
30
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(IndexBookmarksDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'IndexBookmarks', 'mutation', variables);
31
+ },
32
+ IndexTweets(variables, requestHeaders) {
33
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(IndexTweetsDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'IndexTweets', 'mutation', variables);
34
+ },
35
+ RegenerateSuggestions(variables, requestHeaders) {
36
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(RegenerateSuggestionsDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'RegenerateSuggestions', 'mutation', variables);
37
+ },
38
+ MonitorStatus(variables, requestHeaders) {
39
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(MonitorStatusDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), 'MonitorStatus', 'query', variables);
40
+ }
41
+ };
42
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@1a35e1/sonar-cli",
3
+ "version": "0.1.0",
4
+ "description": "X/Twitter social graph CLI for signal filtering and curation",
5
+ "type": "module",
6
+ "bin": {
7
+ "sonar": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "dependencies": {
20
+ "better-sqlite3": "^11",
21
+ "date-fns": "4.1.0",
22
+ "graphql": "^16.12.0",
23
+ "graphql-request": "^7.4.0",
24
+ "ink": "^6",
25
+ "ink-table": "^3.1.0",
26
+ "pastel": "^3.0.0",
27
+ "react": "^19",
28
+ "zod": "^3.25.76"
29
+ },
30
+ "devDependencies": {
31
+ "@graphql-codegen/cli": "^5.0.5",
32
+ "@graphql-codegen/typescript-graphql-request": "^6.4.0",
33
+ "@types/better-sqlite3": "^7",
34
+ "@types/node": "^22",
35
+ "@types/react": "^19",
36
+ "biome": "^0.3.3",
37
+ "ink-link": "^5.0.0",
38
+ "tsx": "^4",
39
+ "typescript": "^5"
40
+ },
41
+ "scripts": {
42
+ "types": "graphql-codegen --config codegen.ts",
43
+ "sonar": "tsx src/cli.ts",
44
+ "build": "tsc",
45
+ "typecheck": "tsc --noEmit"
46
+ }
47
+ }