@1a35e1/sonar-cli 0.4.0 → 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/README.md CHANGED
@@ -229,10 +229,32 @@ sonar topics suggest --json # raw suggestions as JSON
229
229
 
230
230
  Requires `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` depending on vendor.
231
231
 
232
- ### Pipeline
232
+ ### Account
233
+
234
+ ```bash
235
+ sonar account # list accounts, * marks active
236
+ sonar account add <key> # add account (random name)
237
+ sonar account add <key> --alias work # add with custom name
238
+ sonar account switch <name> # switch active account
239
+ sonar account rename <old> <new> # rename an account
240
+ sonar account remove <name> # remove (--force if active)
241
+ ```
242
+
243
+ ### Refresh
244
+
245
+ ```bash
246
+ sonar refresh # full pipeline (all steps)
247
+ sonar refresh --bookmarks # just sync bookmarks from X
248
+ sonar refresh --likes # just sync likes from X
249
+ sonar refresh --graph # just rebuild social graph
250
+ sonar refresh --tweets # just index tweets
251
+ sonar refresh --suggestions # just regenerate suggestions
252
+ sonar refresh --likes --bookmarks # any combo of flags
253
+ ```
254
+
255
+ ### Status
233
256
 
234
257
  ```bash
235
- sonar refresh # full pipeline: graph → tweets → suggestions
236
258
  sonar status # account status, queue activity
237
259
  sonar status --watch # poll every 2s
238
260
  ```
@@ -245,17 +267,24 @@ sonar later --id <suggestion_id> # save for later
245
267
  sonar archive --id <suggestion_id> # archive a suggestion
246
268
  ```
247
269
 
248
- ### Config
270
+ ### Data
249
271
 
250
272
  ```bash
251
- sonar config # show current config
252
- sonar config setup key=<API_KEY> # set API key
273
+ sonar data pull # download feed/suggestions/topics to local SQLite
274
+ sonar data backup # backup local DB
275
+ sonar data restore --from <path> # restore from backup
276
+ sonar data verify # integrity check
277
+ sonar data path # show DB location
278
+ sonar data sql # query helper
253
279
  ```
254
280
 
255
- ### Sync
281
+ ### Config
256
282
 
257
283
  ```bash
258
- sonar sync bookmarks # sync bookmarks to local SQLite
284
+ sonar config # show current config
285
+ sonar config setup --key=<API_KEY> # legacy setup
286
+ sonar config set vendor anthropic # set AI vendor
287
+ sonar config skill --install # install OpenClaw skill (--force to overwrite)
259
288
  ```
260
289
 
261
290
  ---
@@ -6,10 +6,11 @@ import { writeSkillTo } from '../../lib/skill.js';
6
6
  export const options = zod.object({
7
7
  install: zod.boolean().default(false).describe('Install to ~/.claude/skills/sonar/SKILL.md'),
8
8
  dest: zod.string().optional().describe('Write to a custom path'),
9
+ force: zod.boolean().default(false).describe('Overwrite even if file was modified'),
9
10
  });
10
11
  export default function Skill({ options: flags }) {
11
12
  useEffect(() => {
12
- writeSkillTo(flags.dest, flags.install);
13
+ writeSkillTo(flags.dest, flags.install, flags.force);
13
14
  }, []);
14
15
  return _jsx(Text, { dimColor: true, children: "Generating SKILL.md..." });
15
16
  }
package/dist/lib/skill.js CHANGED
@@ -1,4 +1,5 @@
1
- import { writeFileSync, mkdirSync } from 'node:fs';
1
+ import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
2
3
  import { join, dirname } from 'node:path';
3
4
  import { homedir } from 'node:os';
4
5
  const SKILL_CONTENT = `---
@@ -109,18 +110,33 @@ sonar config nuke --confirm
109
110
  | \`ANTHROPIC_API_KEY\` | Required when vendor is \`anthropic\` |
110
111
  `;
111
112
  const DEFAULT_INSTALL_PATH = join(homedir(), '.claude', 'skills', 'sonar', 'SKILL.md');
112
- export function writeSkillTo(dest, install) {
113
+ function sha256(content) {
114
+ return createHash('sha256').update(content).digest('hex');
115
+ }
116
+ function safeWrite(target, content, force) {
117
+ if (existsSync(target) && !force) {
118
+ const existing = readFileSync(target, 'utf8');
119
+ if (existing === content) {
120
+ process.stdout.write(`SKILL.md is already up to date: ${target}\n`);
121
+ process.exit(0);
122
+ }
123
+ // File exists and differs — user may have customized it
124
+ process.stderr.write(`SKILL.md has been modified: ${target}\n` +
125
+ `Use --force to overwrite, or manually merge.\n` +
126
+ `New version hash: ${sha256(content).slice(0, 8)}\n`);
127
+ process.exit(1);
128
+ }
129
+ mkdirSync(dirname(target), { recursive: true });
130
+ writeFileSync(target, content, 'utf8');
131
+ process.stdout.write(`SKILL.md written to ${target}\n`);
132
+ }
133
+ export function writeSkillTo(dest, install, force) {
113
134
  if (install || dest === '--install') {
114
- const target = DEFAULT_INSTALL_PATH;
115
- mkdirSync(dirname(target), { recursive: true });
116
- writeFileSync(target, SKILL_CONTENT, 'utf8');
117
- process.stdout.write(`SKILL.md written to ${target}\n`);
135
+ safeWrite(DEFAULT_INSTALL_PATH, SKILL_CONTENT, force ?? false);
118
136
  process.exit(0);
119
137
  }
120
138
  if (dest) {
121
- mkdirSync(dirname(dest), { recursive: true });
122
- writeFileSync(dest, SKILL_CONTENT, 'utf8');
123
- process.stdout.write(`SKILL.md written to ${dest}\n`);
139
+ safeWrite(dest, SKILL_CONTENT, force ?? false);
124
140
  process.exit(0);
125
141
  }
126
142
  // Default: print to stdout
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1a35e1/sonar-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "X social graph CLI for signal filtering and curation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,35 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Box, Text, useApp } from 'ink';
4
- import { gql } from '../../lib/client.js';
5
- import { Spinner } from '../../components/Spinner.js';
6
- export default function SyncBookmarks() {
7
- const { exit } = useApp();
8
- const [status, setStatus] = useState('pending');
9
- const [error, setError] = useState(null);
10
- useEffect(() => {
11
- async function run() {
12
- setStatus('running');
13
- try {
14
- await gql('mutation SyncBookmarks { syncBookmarks }');
15
- setStatus('ok');
16
- }
17
- catch (err) {
18
- setStatus('failed');
19
- setError(err instanceof Error ? err.message : String(err));
20
- }
21
- }
22
- run();
23
- }, []);
24
- useEffect(() => {
25
- if (status === 'ok' || status === 'failed')
26
- exit();
27
- }, [status]);
28
- if (status === 'running') {
29
- return _jsx(Spinner, { label: "Syncing bookmarks..." });
30
- }
31
- if (status === 'failed') {
32
- return _jsxs(Text, { color: "red", children: ["Error: ", error] });
33
- }
34
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", children: "\u2713 Bookmark sync queued" }), _jsxs(Text, { dimColor: true, children: ["Run ", _jsx(Text, { color: "cyan", children: "sonar status --watch" }), " to monitor progress."] })] }));
35
- }
@@ -1,35 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Box, Text, useApp } from 'ink';
4
- import { gql } from '../../lib/client.js';
5
- import { Spinner } from '../../components/Spinner.js';
6
- export default function SyncLikes() {
7
- const { exit } = useApp();
8
- const [status, setStatus] = useState('pending');
9
- const [error, setError] = useState(null);
10
- useEffect(() => {
11
- async function run() {
12
- setStatus('running');
13
- try {
14
- await gql('mutation SyncLikes { syncLikes }');
15
- setStatus('ok');
16
- }
17
- catch (err) {
18
- setStatus('failed');
19
- setError(err instanceof Error ? err.message : String(err));
20
- }
21
- }
22
- run();
23
- }, []);
24
- useEffect(() => {
25
- if (status === 'ok' || status === 'failed')
26
- exit();
27
- }, [status]);
28
- if (status === 'running') {
29
- return _jsx(Spinner, { label: "Syncing likes..." });
30
- }
31
- if (status === 'failed') {
32
- return _jsxs(Text, { color: "red", children: ["Error: ", error] });
33
- }
34
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "green", children: "\u2713 Likes sync queued" }), _jsxs(Text, { dimColor: true, children: ["Run ", _jsx(Text, { color: "cyan", children: "sonar status --watch" }), " to monitor progress."] })] }));
35
- }