felo-ai 0.2.45 → 0.2.47

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
@@ -127,23 +127,48 @@ felo apple-buy-advisor "Is it worth upgrading to iPad Air 13?"
127
127
  /apple-buy-advisor Is it worth upgrading to iPad Air 13?
128
128
  ```
129
129
 
130
+ **Twitter Writer** — [full options →](./felo-twitter-writer/SKILL.md)
131
+
132
+ ```bash
133
+ # Use as Claude Code skill
134
+ /felo-twitter-writer Analyze @paulg's tweet style and extract a style DNA
135
+ /felo-twitter-writer Write 3 tweets about AI trends in @paulg's style
136
+ /felo-twitter-writer Write a Twitter thread about why most startups fail
137
+ ```
138
+
139
+ **SuperAgent** — [full options →](./felo-superAgent/README.md)
140
+
141
+ ```bash
142
+ # Use as Claude Code skill
143
+ /felo-superagent What is the latest news about AI?
144
+ /felo-superagent Tell me more --thread-id <thread_short_id>
145
+ ```
146
+
147
+ ```bash
148
+ # Run script directly
149
+ node felo-superAgent/scripts/run_superagent.mjs --query "What is quantum computing?"
150
+ node felo-superAgent/scripts/run_superagent.mjs --query "Tell me more" --thread-id <thread_short_id>
151
+ ```
152
+
130
153
  **[See 40+ more examples →](./docs/EXAMPLES.md)**
131
154
 
132
155
  ---
133
156
 
134
157
  ## Skills Overview
135
158
 
136
- 7 skills across search, content generation, web scraping, social media, knowledge base, and shopping advice:
159
+ 9 skills across search, content generation, web scraping, social media, knowledge base, shopping advice, Twitter writing, and AI conversation:
137
160
 
138
- | Skill | Description | Docs |
139
- | --------------------------- | ------------------------------------------------------------- | ------------------------------- |
140
- | **felo-search** | Real-time web search with AI answers. Triggers automatically. | [→](./felo-search/) |
141
- | **felo-slides** | Generate PPT from a prompt | [→](./felo-slides/) |
142
- | **felo-web-fetch** | Fetch and extract webpage content | [→](./felo-web-fetch/) |
143
- | **felo-youtube-subtitling** | Fetch YouTube video subtitles | [→](./felo-youtube-subtitling/) |
144
- | **felo-x-search** | Search X (Twitter) tweets, users, replies | [→](./felo-x-search/SKILL.md) |
145
- | **felo-livedoc** | Manage knowledge bases and semantic retrieval | [→](./felo-livedoc/) |
146
- | **apple-buy-advisor** | Research and compare Apple products before you buy | [→](./apple-buy-advisor/) |
161
+ | Skill | Description | Docs |
162
+ | --------------------------- | ------------------------------------------------------------- | ----------------------------------------- |
163
+ | **felo-search** | Real-time web search with AI answers. Triggers automatically. | [→](./felo-search/) |
164
+ | **felo-slides** | Generate PPT from a prompt | [→](./felo-slides/) |
165
+ | **felo-web-fetch** | Fetch and extract webpage content | [→](./felo-web-fetch/) |
166
+ | **felo-youtube-subtitling** | Fetch YouTube video subtitles | [→](./felo-youtube-subtitling/) |
167
+ | **felo-x-search** | Search X (Twitter) tweets, users, replies | [→](./felo-x-search/SKILL.md) |
168
+ | **felo-livedoc** | Manage knowledge bases and semantic retrieval | [→](./felo-livedoc/) |
169
+ | **apple-buy-advisor** | Research and compare Apple products before you buy | [→](./apple-buy-advisor/) |
170
+ | **felo-twitter-writer** | Analyze tweet style DNA and compose tweets, threads, X posts | [→](./felo-twitter-writer/SKILL.md) |
171
+ | **felo-superAgent** | AI conversation with real-time streaming, continuous threads | [→](./felo-superAgent/README.md) |
147
172
 
148
173
  ---
149
174
 
@@ -163,6 +188,8 @@ felo apple-buy-advisor "Is it worth upgrading to iPad Air 13?"
163
188
  /plugin install felo-x-search@felo-ai
164
189
  /plugin install felo-livedoc@felo-ai
165
190
  /plugin install apple-buy-advisor@felo-ai
191
+ /plugin install felo-twitter-writer@felo-ai
192
+ /plugin install felo-superAgent@felo-ai
166
193
  ```
167
194
 
168
195
  ### ClawHub
@@ -177,6 +204,8 @@ clawhub install felo-youtube-subtitling
177
204
  clawhub install felo-x-search
178
205
  clawhub install felo-livedoc
179
206
  clawhub install apple-buy-advisor
207
+ clawhub install felo-twitter-writer
208
+ clawhub install felo-superAgent
180
209
  ```
181
210
 
182
211
  ### Gemini CLI
@@ -119,6 +119,11 @@ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-resource SHORT
119
119
  node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-resource SHORT_ID RESOURCE_ID --snippet "New summary" --thumbnail "https://example.com/thumb.png"
120
120
  ```
121
121
 
122
+ **Update resource content (ai_doc type only — also auto-updates snippet from first 2000 bytes):**
123
+ ```bash
124
+ node ~/.agents/skills/felo-livedoc/scripts/run_livedoc.mjs update-resource-content SHORT_ID RESOURCE_ID --content "New content here"
125
+ ```
126
+
122
127
  ### Semantic Retrieval
123
128
 
124
129
  **Route relevant resources by query:**
@@ -240,6 +245,7 @@ The API returns JSON with this structure:
240
245
  - `short_id` — unique identifier (use this for all operations)
241
246
  - `name` — LiveDoc name
242
247
  - `description` — LiveDoc description
248
+ - `is_shared` — `true` if this LiveDoc was shared with you (not owned by you)
243
249
  - `created_at` / `modified_at` — timestamps
244
250
 
245
251
  **Resource object:**
@@ -95,6 +95,7 @@ function formatLiveDoc(doc) {
95
95
  out += `- ID: \`${doc.short_id}\`\n`;
96
96
  if (doc.description) out += `- Description: ${doc.description}\n`;
97
97
  if (doc.icon) out += `- Icon: ${doc.icon}\n`;
98
+ if (doc.is_shared != null) out += `- Shared: ${doc.is_shared}\n`;
98
99
  if (doc.created_at) out += `- Created: ${doc.created_at}\n`;
99
100
  if (doc.modified_at) out += `- Modified: ${doc.modified_at}\n`;
100
101
  out += '\n';
@@ -167,6 +168,7 @@ function usage() {
167
168
  ' upload <short_id> Upload file (--file required, --convert optional)',
168
169
  ' remove-resource <short_id> <resource_id> Delete a resource',
169
170
  ' update-resource <short_id> <resource_id> Update resource title/snippet/thumbnail',
171
+ ' update-resource-content <short_id> <resource_id> Update ai_doc content (--content required)',
170
172
  ' retrieve <short_id> Semantic search (--query required, --resource-ids optional)',
171
173
  ' route <short_id> Route relevant resources by query (--query required)',
172
174
  ' download <short_id> <resource_id> Download source file to disk',
@@ -432,6 +434,16 @@ async function main() {
432
434
  code = 0;
433
435
  break;
434
436
  }
437
+ case 'update-resource-content': {
438
+ if (!shortId || !resourceId) { console.error('ERROR: short_id and resource_id are required'); break; }
439
+ if (!args.content) { console.error('ERROR: --content is required'); break; }
440
+ spinnerId = startSpinner('Updating resource content');
441
+ const payload = await apiRequest('PUT', `/livedocs/${shortId}/resources/${resourceId}/content`, { content: args.content }, apiKey, apiBase, timeoutMs);
442
+ if (json) { console.log(JSON.stringify(payload, null, 2)); }
443
+ else { process.stdout.write('Resource content updated.\n'); }
444
+ code = 0;
445
+ break;
446
+ }
435
447
  case 'retrieve': {
436
448
  if (!shortId) { console.error('ERROR: short_id is required'); break; }
437
449
  if (!args.query) { console.error('ERROR: --query is required'); break; }
@@ -90,6 +90,7 @@ Script behavior:
90
90
 
91
91
  - Creates task via `POST https://openapi.felo.ai/v2/ppts`
92
92
  - Supports optional `--theme <id>` to apply a PPT theme (sends `ppt_config.ai_theme_id`)
93
+ - Supports optional `--livedoc-id <id>` to reuse an existing LiveDoc instead of auto-creating a new one
93
94
  - Supports optional `--task-id <id>` to resume polling an existing task (skips creation)
94
95
  - Polls via `GET https://openapi.felo.ai/v2/tasks/{task_id}/historical`
95
96
  - Treats `COMPLETED`/`SUCCESS` as success terminal (case-insensitive)
@@ -15,6 +15,7 @@ function usage() {
15
15
  ' --query <text> PPT prompt (required unless --task-id is given)',
16
16
  ' --task-id <id> Resume polling an existing task (skip creation)',
17
17
  ' --theme <id> PPT theme ID (from ppt-themes)',
18
+ ' --livedoc-id <id> Reuse an existing LiveDoc instead of auto-creating one',
18
19
  ' --interval <seconds> Poll interval, default 10',
19
20
  ' --max-wait <seconds> Max wait time, default 1800',
20
21
  ' --timeout <seconds> Request timeout, default 60',
@@ -30,6 +31,7 @@ function parseArgs(argv) {
30
31
  query: '',
31
32
  taskId: '',
32
33
  theme: '',
34
+ livedocId: '',
33
35
  intervalSec: DEFAULT_INTERVAL_SEC,
34
36
  maxWaitSec: DEFAULT_MAX_WAIT_SEC,
35
37
  timeoutSec: DEFAULT_TIMEOUT_SEC,
@@ -54,6 +56,9 @@ function parseArgs(argv) {
54
56
  } else if (a === '--theme') {
55
57
  out.theme = argv[i + 1] ?? '';
56
58
  i += 1;
59
+ } else if (a === '--livedoc-id') {
60
+ out.livedocId = argv[i + 1] ?? '';
61
+ i += 1;
57
62
  } else if (a === '--interval') {
58
63
  out.intervalSec = Number.parseInt(argv[i + 1] ?? '', 10);
59
64
  i += 1;
@@ -138,11 +143,14 @@ function extractTaskUrls(historicalData, createData) {
138
143
  };
139
144
  }
140
145
 
141
- async function createTask(apiKey, apiBase, query, timeoutMs, theme) {
146
+ async function createTask(apiKey, apiBase, query, timeoutMs, theme, livedocId) {
142
147
  const reqBody = { query };
143
148
  if (theme) {
144
149
  reqBody.ppt_config = { ai_theme_id: theme };
145
150
  }
151
+ if (livedocId) {
152
+ reqBody.livedoc_short_id = livedocId;
153
+ }
146
154
  const payload = await fetchJson(
147
155
  `${apiBase}/v2/ppts`,
148
156
  {
@@ -211,7 +219,7 @@ async function main() {
211
219
  }
212
220
  } else {
213
221
  // Create a new task
214
- createData = await createTask(apiKey, apiBase, args.query, timeoutMs, args.theme);
222
+ createData = await createTask(apiKey, apiBase, args.query, timeoutMs, args.theme, args.livedocId || undefined);
215
223
  taskId = createData.task_id;
216
224
  if (args.verbose) {
217
225
  console.error(`Task ID: ${taskId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "felo-ai",
3
- "version": "0.2.45",
3
+ "version": "0.2.47",
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",
@@ -32,7 +32,8 @@
32
32
  "url": "git+https://github.com/Felo-Inc/felo-skills.git"
33
33
  },
34
34
  "dependencies": {
35
- "commander": "^12.0.0"
35
+ "commander": "^12.0.0",
36
+ "felo-ai": "^0.2.43"
36
37
  },
37
38
  "scripts": {
38
39
  "test": "node --test tests/",
package/src/cli.js CHANGED
@@ -89,6 +89,7 @@ program
89
89
  )
90
90
  .option("--theme <id>", "PPT theme ID (from ppt-themes command)")
91
91
  .option("--task-id <id>", "resume polling an existing task (skip creation)")
92
+ .option("--livedoc-id <id>", "reuse an existing LiveDoc short_id instead of auto-creating a new one")
92
93
  .action(async (query, opts) => {
93
94
  if (!query && !opts.taskId) {
94
95
  console.error("Error: provide a <query> or --task-id to resume an existing task");
@@ -105,6 +106,7 @@ program
105
106
  pollTimeoutMs: Number.isNaN(pollTimeoutMs) ? 1_200_000 : pollTimeoutMs,
106
107
  pptConfig,
107
108
  taskId: opts.taskId,
109
+ livedocShortId: opts.livedocId || undefined,
108
110
  });
109
111
  process.exitCode = code;
110
112
  flushStdioThenExit(code);
@@ -774,6 +776,23 @@ livedocCmd
774
776
  flushStdioThenExit(code);
775
777
  });
776
778
 
779
+ livedocCmd
780
+ .command("update-resource-content <short_id> <resource_id>")
781
+ .description("Update the content of an ai_doc resource (also auto-updates snippet)")
782
+ .requiredOption("--content <text>", "new content for the resource")
783
+ .option("-j, --json", "output raw JSON")
784
+ .option("-t, --timeout <seconds>", "request timeout in seconds", "60")
785
+ .action(async (shortId, resourceId, opts) => {
786
+ const timeoutMs = parseInt(opts.timeout, 10) * 1000;
787
+ const code = await livedoc.updateResourceContent(shortId, resourceId, {
788
+ content: opts.content,
789
+ json: opts.json,
790
+ timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
791
+ });
792
+ process.exitCode = code;
793
+ flushStdioThenExit(code);
794
+ });
795
+
777
796
  livedocCmd
778
797
  .command("content <short_id> <resource_id>")
779
798
  .description("Get extracted text content of a resource")
package/src/livedoc.js CHANGED
@@ -91,6 +91,7 @@ function formatLiveDoc(doc) {
91
91
  out += `- ID: \`${doc.short_id}\`\n`;
92
92
  if (doc.description) out += `- Description: ${doc.description}\n`;
93
93
  if (doc.icon) out += `- Icon: ${doc.icon}\n`;
94
+ if (doc.is_shared != null) out += `- Shared: ${doc.is_shared}\n`;
94
95
  if (doc.created_at) out += `- Created: ${doc.created_at}\n`;
95
96
  if (doc.modified_at) out += `- Modified: ${doc.modified_at}\n`;
96
97
  out += '\n';
@@ -823,6 +824,28 @@ export async function createTaskComment(shortId, taskId, opts = {}) {
823
824
  } finally { stopSpinner(spinnerId); }
824
825
  }
825
826
 
827
+ export async function updateResourceContent(shortId, resourceId, opts = {}) {
828
+ const apiKey = await getApiKey();
829
+ if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
830
+ if (!shortId) { process.stderr.write('ERROR: short_id is required.\n'); return 1; }
831
+ if (!resourceId) { process.stderr.write('ERROR: resource_id is required.\n'); return 1; }
832
+ if (!opts.content) { process.stderr.write('ERROR: --content is required.\n'); return 1; }
833
+
834
+ const apiBase = await getApiBase();
835
+ const timeoutMs = opts.timeoutMs || DEFAULT_TIMEOUT_MS;
836
+ const spinnerId = startSpinner('Updating resource content');
837
+
838
+ try {
839
+ const payload = await apiRequest('PUT', `/livedocs/${shortId}/resources/${resourceId}/content`, { content: opts.content }, apiKey, apiBase, timeoutMs);
840
+ if (opts.json) { console.log(JSON.stringify(payload, null, 2)); return 0; }
841
+ process.stdout.write('Resource content updated.\n');
842
+ return 0;
843
+ } catch (err) {
844
+ process.stderr.write(`Failed to update resource content: ${err?.message || err}\n`);
845
+ return 1;
846
+ } finally { stopSpinner(spinnerId); }
847
+ }
848
+
826
849
  export async function pptRetrieve(shortId, opts = {}) {
827
850
  const apiKey = await getApiKey();
828
851
  if (!apiKey) { console.error(NO_KEY_MESSAGE.trim()); return 1; }
package/src/slides.js CHANGED
@@ -53,13 +53,17 @@ function normalizeTaskStatus(status) {
53
53
  * @param {number} timeoutMs
54
54
  * @param {string} apiBase
55
55
  * @param {{ ai_theme_id?: string }} [pptConfig]
56
+ * @param {string} [livedocShortId]
56
57
  */
57
- async function createPptTask(apiKey, query, timeoutMs, apiBase, pptConfig) {
58
+ async function createPptTask(apiKey, query, timeoutMs, apiBase, pptConfig, livedocShortId) {
58
59
  const url = `${apiBase}/v2/ppts`;
59
60
  const body = { query: query.trim() };
60
61
  if (pptConfig && Object.keys(pptConfig).length > 0) {
61
62
  body.ppt_config = pptConfig;
62
63
  }
64
+ if (livedocShortId) {
65
+ body.livedoc_short_id = livedocShortId;
66
+ }
63
67
  const res = await fetchWithTimeoutAndRetry(
64
68
  url,
65
69
  {
@@ -176,7 +180,8 @@ export async function slides(query, options = {}) {
176
180
  query,
177
181
  requestTimeoutMs,
178
182
  apiBase,
179
- options.pptConfig
183
+ options.pptConfig,
184
+ options.livedocShortId
180
185
  );
181
186
  taskId = createResult.task_id;
182
187