@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 +36 -7
- package/dist/commands/config/skill.js +2 -1
- package/dist/lib/skill.js +25 -9
- package/package.json +1 -1
- package/dist/commands/sync/bookmarks.js +0 -35
- package/dist/commands/sync/likes.js +0 -35
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
|
-
###
|
|
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
|
-
###
|
|
270
|
+
### Data
|
|
249
271
|
|
|
250
272
|
```bash
|
|
251
|
-
sonar
|
|
252
|
-
sonar
|
|
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
|
-
###
|
|
281
|
+
### Config
|
|
256
282
|
|
|
257
283
|
```bash
|
|
258
|
-
sonar
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
-
}
|