felo-ai 0.2.35 → 0.2.42
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 +7 -0
- package/felo-livedoc/SKILL.md +3 -0
- package/felo-livedoc/scripts/run_livedoc.mjs +8 -2
- package/package.json +1 -1
- package/src/appleBuyAdvisor.js +17 -0
- package/src/cli.js +31 -0
- package/src/livedoc.js +5 -1
- package/tests/cli.test.js +26 -0
package/README.md
CHANGED
|
@@ -113,6 +113,13 @@ felo livedoc retrieve SHORT_ID --query "search query"
|
|
|
113
113
|
|
|
114
114
|
**Apple Buy Advisor** — [full options →](./apple-buy-advisor/SKILL.md)
|
|
115
115
|
|
|
116
|
+
```bash
|
|
117
|
+
# Use as Felo CLI command
|
|
118
|
+
felo apple-buy-advisor "Should I buy MacBook Pro M4?"
|
|
119
|
+
felo apple-buy-advisor "Compare iPhone 17 vs iPhone 17e"
|
|
120
|
+
felo apple-buy-advisor "Is it worth upgrading to iPad Air 13?"
|
|
121
|
+
```
|
|
122
|
+
|
|
116
123
|
```bash
|
|
117
124
|
# Use as Claude Code skill
|
|
118
125
|
/apple-buy-advisor Should I buy MacBook Pro M4?
|
package/felo-livedoc/SKILL.md
CHANGED
|
@@ -191,6 +191,7 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs tasks SHORT_ID --labe
|
|
|
191
191
|
```bash
|
|
192
192
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs create-task SHORT_ID --title "Write docs" --status 0 --sort 0
|
|
193
193
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs create-task SHORT_ID --title "Write docs" --status 0 --sort 0 --description "API docs" --labels "docs"
|
|
194
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs create-task SHORT_ID --title "Write docs" --operated-by "claude-code"
|
|
194
195
|
```
|
|
195
196
|
|
|
196
197
|
Task status values: `0`=TODO, `1`=IN_PROGRESS, `2`=DONE
|
|
@@ -199,6 +200,7 @@ Task status values: `0`=TODO, `1`=IN_PROGRESS, `2`=DONE
|
|
|
199
200
|
```bash
|
|
200
201
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-task SHORT_ID TASK_ID --status 1
|
|
201
202
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-task SHORT_ID TASK_ID --title "New title" --labels "docs,done"
|
|
203
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-task SHORT_ID TASK_ID --status 2 --operated-by "claude-code"
|
|
202
204
|
```
|
|
203
205
|
|
|
204
206
|
**Delete a task:**
|
|
@@ -215,6 +217,7 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs task-records SHORT_ID
|
|
|
215
217
|
**Add a comment to a task:**
|
|
216
218
|
```bash
|
|
217
219
|
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs add-task-comment SHORT_ID TASK_ID --content "This is a comment."
|
|
220
|
+
node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs add-task-comment SHORT_ID TASK_ID --content "This is a comment." --operated-by "claude-code"
|
|
218
221
|
```
|
|
219
222
|
### Options
|
|
220
223
|
|
|
@@ -208,6 +208,7 @@ function usage() {
|
|
|
208
208
|
' --sort <n> Task sort order (non-negative integer)',
|
|
209
209
|
' --labels <labels> Comma-separated labels (tasks)',
|
|
210
210
|
' --record-type <type> Record type filter: comment, edit, status_change',
|
|
211
|
+
' --operated-by <sig> Operator signature, e.g. claude-code, openclaw (max 100 chars)',
|
|
211
212
|
' -j, --json Output raw JSON',
|
|
212
213
|
' -t, --timeout <ms> Timeout in ms (default: 60000)',
|
|
213
214
|
' --help Show this help',
|
|
@@ -219,7 +220,7 @@ function parseArgs(argv) {
|
|
|
219
220
|
keyword: '', page: '', size: '', type: '', content: '', title: '',
|
|
220
221
|
urls: '', file: '', convert: false, query: '', resourceIds: '', maxResources: '',
|
|
221
222
|
resourceId: '', pageNumber: '', maxChunk: '', expiresIn: '', output: '',
|
|
222
|
-
status: '', sort: '', labels: '', recordType: '',
|
|
223
|
+
status: '', sort: '', labels: '', recordType: '', operatedBy: '',
|
|
223
224
|
json: false, timeoutMs: DEFAULT_TIMEOUT_MS, help: false,
|
|
224
225
|
};
|
|
225
226
|
const positional = [];
|
|
@@ -251,6 +252,7 @@ function parseArgs(argv) {
|
|
|
251
252
|
else if (a === '--sort') out.sort = argv[++i] || '';
|
|
252
253
|
else if (a === '--labels') out.labels = argv[++i] || '';
|
|
253
254
|
else if (a === '--record-type') out.recordType = argv[++i] || '';
|
|
255
|
+
else if (a === '--operated-by') out.operatedBy = argv[++i] || '';
|
|
254
256
|
else if (a === '-t' || a === '--timeout') {
|
|
255
257
|
const n = parseInt(argv[++i] || '', 10);
|
|
256
258
|
if (Number.isFinite(n) && n > 0) out.timeoutMs = n;
|
|
@@ -615,6 +617,7 @@ async function main() {
|
|
|
615
617
|
const body = { title: args.title, status, sort };
|
|
616
618
|
if (args.description) body.description = args.description;
|
|
617
619
|
body.labels = args.labels ? args.labels.split(',').map(l => l.trim()).filter(Boolean) : [];
|
|
620
|
+
if (args.operatedBy) body.operated_by = args.operatedBy;
|
|
618
621
|
const payload = await apiRequest('POST', `/livedocs/${shortId}/tasks`, body, apiKey, apiBase, timeoutMs);
|
|
619
622
|
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
620
623
|
else { process.stdout.write('Task created!\n\n'); process.stdout.write(formatTask(payload?.data)); }
|
|
@@ -631,6 +634,7 @@ async function main() {
|
|
|
631
634
|
if (args.status !== '') body.status = parseInt(args.status, 10);
|
|
632
635
|
if (args.sort !== '') body.sort = parseInt(args.sort, 10);
|
|
633
636
|
if (args.labels !== undefined) body.labels = args.labels.split(',').map(l => l.trim()).filter(Boolean);
|
|
637
|
+
if (args.operatedBy) body.operated_by = args.operatedBy;
|
|
634
638
|
const payload = await apiRequest('PATCH', `/livedocs/${shortId}/tasks/${resourceId}`, body, apiKey, apiBase, timeoutMs);
|
|
635
639
|
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
636
640
|
else { process.stdout.write('Task updated!\n\n'); process.stdout.write(formatTask(payload?.data)); }
|
|
@@ -674,7 +678,9 @@ async function main() {
|
|
|
674
678
|
if (!resourceId) { console.error('ERROR: task_id is required'); break; }
|
|
675
679
|
if (!args.content) { console.error('ERROR: --content is required'); break; }
|
|
676
680
|
spinnerId = startSpinner('Adding comment');
|
|
677
|
-
const
|
|
681
|
+
const body = { content: args.content };
|
|
682
|
+
if (args.operatedBy) body.operated_by = args.operatedBy;
|
|
683
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/tasks/${resourceId}/comments`, body, apiKey, apiBase, timeoutMs);
|
|
678
684
|
if (json) { console.log(JSON.stringify(payload, null, 2)); }
|
|
679
685
|
else { process.stdout.write('Comment added.\n'); process.stdout.write(formatTaskRecord(payload?.data)); }
|
|
680
686
|
code = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "felo-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"description": "Felo AI CLI - real-time search, PPT generation, SuperAgent conversation, LiveDoc management, web fetch, YouTube subtitles, LiveDoc knowledge base, and X (Twitter) search from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/cli.js",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { superAgent } from './superAgent.js';
|
|
2
|
+
|
|
3
|
+
function buildAppleBuyAdvisorPrompt(query) {
|
|
4
|
+
const trimmed = String(query || '').trim();
|
|
5
|
+
return [
|
|
6
|
+
`/apple-buy-advisor ${trimmed}`,
|
|
7
|
+
'',
|
|
8
|
+
'If slash-style skill routing is unavailable, still act as an Apple product buy advisor.',
|
|
9
|
+
'Use current information, compare relevant Apple models, summarize user and professional feedback when possible, and end with a clear buying recommendation.',
|
|
10
|
+
].join('\n');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function appleBuyAdvisor(query, options = {}) {
|
|
14
|
+
return superAgent(buildAppleBuyAdvisorPrompt(query), options);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export { buildAppleBuyAdvisorPrompt };
|
package/src/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
import { search } from "./search.js";
|
|
6
6
|
import { slides, listPptThemes } from "./slides.js";
|
|
7
7
|
import { superAgent, listLiveDocs, listLiveDocResources } from "./superAgent.js";
|
|
8
|
+
import { appleBuyAdvisor } from "./appleBuyAdvisor.js";
|
|
8
9
|
import { webFetch } from "./webFetch.js";
|
|
9
10
|
import { youtubeSubtitling } from "./youtubeSubtitling.js";
|
|
10
11
|
import { contentToSlides } from "./contentToSlides.js";
|
|
@@ -176,6 +177,30 @@ program
|
|
|
176
177
|
flushStdioThenExit(code);
|
|
177
178
|
});
|
|
178
179
|
|
|
180
|
+
program
|
|
181
|
+
.command("apple-buy-advisor")
|
|
182
|
+
.description("Research and compare Apple products before you buy")
|
|
183
|
+
.argument("<query>", "Apple product buying or comparison question")
|
|
184
|
+
.option("-j, --json", "output JSON with answer, thread_short_id, live_doc_short_id")
|
|
185
|
+
.option("-v, --verbose", "log stream key, thread ID, LiveDoc ID to stderr")
|
|
186
|
+
.option("-t, --timeout <seconds>", "request/stream timeout in seconds", "60")
|
|
187
|
+
.option("--live-doc-id <id>", "reuse existing LiveDoc short_id for continuous conversation")
|
|
188
|
+
.option("--thread-id <id>", "existing thread/conversation ID for follow-up questions")
|
|
189
|
+
.option("--accept-language <lang>", "language preference (e.g. zh, en)")
|
|
190
|
+
.action(async (query, opts) => {
|
|
191
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
192
|
+
const code = await appleBuyAdvisor(query, {
|
|
193
|
+
json: opts.json,
|
|
194
|
+
verbose: opts.verbose,
|
|
195
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
196
|
+
liveDocId: opts.liveDocId || undefined,
|
|
197
|
+
threadId: opts.threadId || undefined,
|
|
198
|
+
acceptLanguage: opts.acceptLanguage || undefined,
|
|
199
|
+
});
|
|
200
|
+
process.exitCode = code;
|
|
201
|
+
flushStdioThenExit(code);
|
|
202
|
+
});
|
|
203
|
+
|
|
179
204
|
program
|
|
180
205
|
.command("livedocs")
|
|
181
206
|
.description("List LiveDocs with pagination and optional keyword filtering")
|
|
@@ -886,6 +911,7 @@ livedocCmd
|
|
|
886
911
|
.option("--sort <n>", "sort order (non-negative integer, default: 0)")
|
|
887
912
|
.option("--description <desc>", "task description")
|
|
888
913
|
.option("--labels <labels>", "comma-separated labels (max 10)")
|
|
914
|
+
.option("--operated-by <signature>", "operator signature, e.g. claude-code, openclaw (max 100 characters)")
|
|
889
915
|
.option("-j, --json", "output raw JSON")
|
|
890
916
|
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
891
917
|
.action(async (shortId, opts) => {
|
|
@@ -896,6 +922,7 @@ livedocCmd
|
|
|
896
922
|
sort: opts.sort,
|
|
897
923
|
description: opts.description,
|
|
898
924
|
labels: opts.labels,
|
|
925
|
+
operatedBy: opts.operatedBy,
|
|
899
926
|
json: opts.json,
|
|
900
927
|
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
901
928
|
});
|
|
@@ -911,6 +938,7 @@ livedocCmd
|
|
|
911
938
|
.option("--status <n>", "new status: 0=TODO, 1=IN_PROGRESS, 2=DONE")
|
|
912
939
|
.option("--sort <n>", "new sort order")
|
|
913
940
|
.option("--labels <labels>", "new comma-separated labels")
|
|
941
|
+
.option("--operated-by <signature>", "operator signature, e.g. claude-code, openclaw (max 100 characters)")
|
|
914
942
|
.option("-j, --json", "output raw JSON")
|
|
915
943
|
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
916
944
|
.action(async (shortId, taskId, opts) => {
|
|
@@ -921,6 +949,7 @@ livedocCmd
|
|
|
921
949
|
status: opts.status,
|
|
922
950
|
sort: opts.sort,
|
|
923
951
|
labels: opts.labels,
|
|
952
|
+
operatedBy: opts.operatedBy,
|
|
924
953
|
json: opts.json,
|
|
925
954
|
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
926
955
|
});
|
|
@@ -968,12 +997,14 @@ livedocCmd
|
|
|
968
997
|
.command("add-task-comment <short_id> <task_id>")
|
|
969
998
|
.description("Add a comment to a task")
|
|
970
999
|
.requiredOption("--content <text>", "comment content")
|
|
1000
|
+
.option("--operated-by <signature>", "operator signature, e.g. claude-code, openclaw (max 100 characters)")
|
|
971
1001
|
.option("-j, --json", "output raw JSON")
|
|
972
1002
|
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
973
1003
|
.action(async (shortId, taskId, opts) => {
|
|
974
1004
|
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
975
1005
|
const code = await livedoc.createTaskComment(shortId, taskId, {
|
|
976
1006
|
content: opts.content,
|
|
1007
|
+
operatedBy: opts.operatedBy,
|
|
977
1008
|
json: opts.json,
|
|
978
1009
|
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
979
1010
|
});
|
package/src/livedoc.js
CHANGED
|
@@ -711,6 +711,7 @@ export async function createTask(shortId, opts = {}) {
|
|
|
711
711
|
const body = { title: opts.title, status, sort };
|
|
712
712
|
if (opts.description) body.description = opts.description;
|
|
713
713
|
body.labels = opts.labels ? opts.labels.split(',').map(l => l.trim()).filter(Boolean) : [];
|
|
714
|
+
if (opts.operatedBy) body.operated_by = opts.operatedBy;
|
|
714
715
|
const payload = await apiRequest('POST', `/livedocs/${shortId}/tasks`, body, apiKey, apiBase, timeoutMs);
|
|
715
716
|
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
716
717
|
process.stdout.write('Task created!\n\n');
|
|
@@ -738,6 +739,7 @@ export async function updateTask(shortId, taskId, opts = {}) {
|
|
|
738
739
|
if (opts.status !== undefined && opts.status !== '') body.status = parseInt(opts.status, 10);
|
|
739
740
|
if (opts.sort !== undefined && opts.sort !== '') body.sort = parseInt(opts.sort, 10);
|
|
740
741
|
if (opts.labels !== undefined) body.labels = opts.labels.split(',').map(l => l.trim()).filter(Boolean);
|
|
742
|
+
if (opts.operatedBy) body.operated_by = opts.operatedBy;
|
|
741
743
|
const payload = await apiRequest('PATCH', `/livedocs/${shortId}/tasks/${taskId}`, body, apiKey, apiBase, timeoutMs);
|
|
742
744
|
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
743
745
|
process.stdout.write('Task updated!\n\n');
|
|
@@ -808,7 +810,9 @@ export async function createTaskComment(shortId, taskId, opts = {}) {
|
|
|
808
810
|
const spinnerId = startSpinner('Adding comment');
|
|
809
811
|
|
|
810
812
|
try {
|
|
811
|
-
const
|
|
813
|
+
const body = { content: opts.content };
|
|
814
|
+
if (opts.operatedBy) body.operated_by = opts.operatedBy;
|
|
815
|
+
const payload = await apiRequest('POST', `/livedocs/${shortId}/tasks/${taskId}/comments`, body, apiKey, apiBase, timeoutMs);
|
|
812
816
|
if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
|
|
813
817
|
process.stdout.write('Comment added.\n');
|
|
814
818
|
process.stdout.write(formatTaskRecord(payload?.data));
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const cliPath = path.resolve('src/cli.js');
|
|
7
|
+
|
|
8
|
+
describe('CLI command registration', () => {
|
|
9
|
+
it('shows apple-buy-advisor in top-level help', () => {
|
|
10
|
+
const result = spawnSync(process.execPath, [cliPath, '--help'], {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
assert.strictEqual(result.status, 0);
|
|
15
|
+
assert.match(result.stdout, /apple-buy-advisor/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('shows help for apple-buy-advisor command', () => {
|
|
19
|
+
const result = spawnSync(process.execPath, [cliPath, 'apple-buy-advisor', '--help'], {
|
|
20
|
+
encoding: 'utf8',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
assert.strictEqual(result.status, 0);
|
|
24
|
+
assert.match(result.stdout, /Research and compare Apple products before you buy/);
|
|
25
|
+
});
|
|
26
|
+
});
|