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 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?
@@ -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 payload = await apiRequest('POST', `/livedocs/${shortId}/tasks/${resourceId}/comments`, { content: args.content }, apiKey, apiBase, timeoutMs);
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.35",
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 payload = await apiRequest('POST', `/livedocs/${shortId}/tasks/${taskId}/comments`, { content: opts.content }, apiKey, apiBase, timeoutMs);
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
+ });