felo-ai 0.2.46 → 0.2.48
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 +163 -51
- package/felo-livedoc/SKILL.md +6 -0
- package/felo-livedoc/scripts/run_livedoc.mjs +12 -0
- package/felo-slides/SKILL.md +1 -0
- package/felo-slides/scripts/run_ppt_task.mjs +10 -2
- package/felo-superAgent/README.md +223 -280
- package/felo-superAgent/SKILL.md +281 -47
- package/felo-superAgent/scripts/run_style_library.mjs +213 -0
- package/felo-twitter-writer/README.md +117 -13
- package/felo-twitter-writer/SKILL.md +276 -41
- package/package.json +1 -1
- package/src/cli.js +38 -1
- package/src/livedoc.js +23 -0
- package/src/slides.js +7 -2
- package/src/superAgent.js +133 -0
|
@@ -11,21 +11,28 @@ These rules are mandatory. Violating any of them will produce incorrect behavior
|
|
|
11
11
|
|
|
12
12
|
1. **This skill uses SuperAgent directly.** All generation is handled by `felo-superAgent/scripts/run_superagent.mjs` with `--skill-id twitter-writer`. Do NOT attempt to generate tweet content yourself.
|
|
13
13
|
|
|
14
|
-
2. **
|
|
14
|
+
2. **ALWAYS use `--json` flag** when calling SuperAgent. In Claude Code's Bash tool, stdout is always captured — it never streams directly to the user. JSON mode returns the full answer in a structured response. After the script finishes, read `data.answer` from the JSON output and print it verbatim as your response text.
|
|
15
15
|
|
|
16
|
-
3. **
|
|
16
|
+
3. **ALWAYS output `data.answer` verbatim.** After the script finishes, print `data.answer` exactly as-is as your response text. Do NOT summarize, paraphrase, or add commentary around it.
|
|
17
17
|
|
|
18
18
|
4. **`--live-doc-id` is REQUIRED** for every SuperAgent call. Follow the livedoc reuse rules from `felo-superAgent/SKILL.md`:
|
|
19
19
|
- Reuse any `live_doc_id` already available in this session
|
|
20
|
-
- If none: run `node felo-livedoc/scripts/run_livedoc.mjs list --json`, use `items[0].short_id`
|
|
20
|
+
- If none: run `node felo-livedoc/scripts/run_livedoc.mjs list --json`, use `data.items[0].short_id`
|
|
21
21
|
- If list is empty: run `node felo-livedoc/scripts/run_livedoc.mjs create --name "Twitter Writer" --json`, use `data.short_id`
|
|
22
22
|
|
|
23
|
-
5. **Always persist state.** After every SuperAgent call, extract `thread_short_id` and `live_doc_short_id` from the
|
|
23
|
+
5. **Always persist state.** After every SuperAgent call, extract `thread_short_id` and `live_doc_short_id` from the JSON response fields `data.thread_short_id` and `data.live_doc_short_id`. Use them in subsequent calls.
|
|
24
24
|
|
|
25
25
|
6. **Output language follows the user's input language.** Default is `en`. Detect the user's language and pass the matching `--accept-language` value: `ja` for Japanese, `en` for English, `ko` for Korean, `zh` for Chinese. If unsure, use `en`.
|
|
26
26
|
|
|
27
27
|
7. **Do NOT pass `--timeout` to the SuperAgent script.** The script manages its own connection lifecycle.
|
|
28
28
|
|
|
29
|
+
8. **Brand style selection for Mode 2 new conversations only.** When starting a new conversation for content creation (Mode 2, no `thread_short_id`), you MUST attempt to fetch the TWITTER style library and offer the user a style choice BEFORE calling SuperAgent. The style is passed via `--ext '{"brand_style_requirement":"..."}'`. Full procedure in the Style Selection section below.
|
|
30
|
+
|
|
31
|
+
- Style category is always `TWITTER` (hardcoded — this skill only writes tweets).
|
|
32
|
+
- `--ext` is only valid for new conversations. Never pass it in follow-up mode (`--thread-id`).
|
|
33
|
+
- If the style library returns no entries, skip silently and proceed without `--ext`.
|
|
34
|
+
- Mode 1 (style DNA extraction) does NOT use this step.
|
|
35
|
+
|
|
29
36
|
## When to Use
|
|
30
37
|
|
|
31
38
|
Trigger this skill when the user wants to:
|
|
@@ -82,6 +89,8 @@ Determine conversation mode first:
|
|
|
82
89
|
- If **no** `thread_short_id` exists in this session → new conversation (pass `--skill-id twitter-writer`)
|
|
83
90
|
- If `thread_short_id` **already exists** in this session → follow-up (pass `--thread-id`)
|
|
84
91
|
|
|
92
|
+
Replace `LANG` with the user's language: `en` (English), `zh` (Chinese), `ja` (Japanese), `ko` (Korean). See Constraint #6.
|
|
93
|
+
|
|
85
94
|
**New conversation (first call in session):**
|
|
86
95
|
|
|
87
96
|
```bash
|
|
@@ -89,7 +98,8 @@ node felo-superAgent/scripts/run_superagent.mjs \
|
|
|
89
98
|
--query "/twitter-writer ENRICHED_QUERY_WITH_TWEET_CONTENT" \
|
|
90
99
|
--live-doc-id "LIVE_DOC_ID" \
|
|
91
100
|
--skill-id twitter-writer \
|
|
92
|
-
--accept-language LANG
|
|
101
|
+
--accept-language LANG \
|
|
102
|
+
--json
|
|
93
103
|
```
|
|
94
104
|
|
|
95
105
|
**Follow-up (thread_short_id already exists):**
|
|
@@ -99,7 +109,8 @@ node felo-superAgent/scripts/run_superagent.mjs \
|
|
|
99
109
|
--query "/twitter-writer ENRICHED_QUERY_WITH_TWEET_CONTENT" \
|
|
100
110
|
--thread-id "THREAD_SHORT_ID" \
|
|
101
111
|
--live-doc-id "LIVE_DOC_ID" \
|
|
102
|
-
--accept-language LANG
|
|
112
|
+
--accept-language LANG \
|
|
113
|
+
--json
|
|
103
114
|
```
|
|
104
115
|
|
|
105
116
|
**Query construction example:**
|
|
@@ -117,7 +128,7 @@ Keep the query under 2000 characters. If tweet content is too long, include the
|
|
|
117
128
|
|
|
118
129
|
#### Step 4: Save state
|
|
119
130
|
|
|
120
|
-
Extract `thread_short_id` and `live_doc_short_id` from
|
|
131
|
+
Extract `thread_short_id` and `live_doc_short_id` from the JSON response fields `data.thread_short_id` and `data.live_doc_short_id`. Save for follow-up calls.
|
|
121
132
|
|
|
122
133
|
---
|
|
123
134
|
|
|
@@ -132,7 +143,97 @@ Extract `thread_short_id` and `live_doc_short_id` from stderr `[state]` line. Sa
|
|
|
132
143
|
- If Mode 1 was just run in this session → style DNA is already in the LiveDoc canvas, use follow-up mode
|
|
133
144
|
- If user provides a style DNA directly → include it in the query
|
|
134
145
|
- If user provides an account name → run Mode 1 first, then continue with Mode 2
|
|
135
|
-
- If no style DNA → proceed
|
|
146
|
+
- If no style DNA → proceed to style library selection (Step 1.5)
|
|
147
|
+
|
|
148
|
+
#### Step 1.5: Brand Style Selection (new conversations only)
|
|
149
|
+
|
|
150
|
+
**Only run this step when:**
|
|
151
|
+
- This is a **new conversation** (no `thread_short_id` in session), AND
|
|
152
|
+
- Mode 1 was NOT just run (i.e., there is no existing thread carrying style DNA context)
|
|
153
|
+
|
|
154
|
+
**Skip this step entirely when:**
|
|
155
|
+
- `thread_short_id` already exists (follow-up) — `--ext` has no effect in follow-up mode
|
|
156
|
+
- Mode 1 was just run in this session — style context is already in the thread
|
|
157
|
+
|
|
158
|
+
**1.5a. Check if user already specified a style:**
|
|
159
|
+
|
|
160
|
+
If the user mentioned a style by name (e.g., "use my Bold Voice style") or pasted a style block, note it and skip to step 1.5d.
|
|
161
|
+
|
|
162
|
+
**1.5b. Fetch the TWITTER style library (names only):**
|
|
163
|
+
|
|
164
|
+
IMPORTANT: The style library output is very large (each Style DNA can be thousands of characters). Always use `--json` and extract only names/labels to avoid Bash tool output truncation. Never call `run_style_library.mjs` without `--json` for listing purposes.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language LANG --json | node -e "
|
|
168
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
169
|
+
const j=JSON.parse(d);
|
|
170
|
+
const list=j.list||[];
|
|
171
|
+
const user=list.filter(s=>!s.recommended);
|
|
172
|
+
const rec=list.filter(s=>s.recommended);
|
|
173
|
+
if(user.length) { console.log('[Your styles]'); user.forEach((s,i)=>{ const labels=(s.content?.labels?.LANG||s.content?.labels?.en||[]).join(', '); console.log((i+1)+'. '+s.name+(labels?' — '+labels:'')); }); }
|
|
174
|
+
if(rec.length) { console.log('[Recommended styles]'); rec.forEach((s,i)=>{ const labels=(s.content?.labels?.LANG||s.content?.labels?.en||[]).join(', '); console.log((user.length+i+1)+'. '+s.name+(labels?' — '+labels:'')); }); }
|
|
175
|
+
if(!list.length) console.log('(No styles found)');
|
|
176
|
+
"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Replace `LANG` with the user's language value (`zh`, `ja`, `ko`, `en`) in both `--accept-language` and inside the node script's `labels?.LANG` references.
|
|
180
|
+
|
|
181
|
+
Always pass `--accept-language` matching the user's language (same value used for SuperAgent).
|
|
182
|
+
|
|
183
|
+
**1.5c. Handle the result:**
|
|
184
|
+
|
|
185
|
+
**If the list is empty:** Skip silently. Proceed to Step 2 without `--ext`.
|
|
186
|
+
|
|
187
|
+
**If styles are available:** Present them to the user grouped by type, showing names only. Always include a "no preference" option. Wait for the user's reply before proceeding.
|
|
188
|
+
|
|
189
|
+
Example presentation (adapt language to match user's language):
|
|
190
|
+
```
|
|
191
|
+
Here are the available Twitter writing styles — choosing one will make the output more accurate:
|
|
192
|
+
|
|
193
|
+
[Your styles]
|
|
194
|
+
1. My Bold Voice
|
|
195
|
+
2. darioamodei
|
|
196
|
+
|
|
197
|
+
[Recommended styles]
|
|
198
|
+
3. Casual & Witty
|
|
199
|
+
|
|
200
|
+
0. No preference — use default style
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**1.5d. Build `--ext` from the chosen style:**
|
|
204
|
+
|
|
205
|
+
After the user selects a style, fetch the full Style DNA for that specific style using `--json` and extract it in Node.js. Do NOT re-read the full text output — it will be truncated by the Bash tool.
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language LANG --json | node -e "
|
|
209
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
210
|
+
const j=JSON.parse(d);
|
|
211
|
+
const s=(j.list||[]).find(s=>s.name==='CHOSEN_STYLE_NAME');
|
|
212
|
+
if(!s){process.stderr.write('Style not found\n');process.exit(1);}
|
|
213
|
+
const labels=(s.content?.labels?.LANG||s.content?.labels?.en||[]).join(', ');
|
|
214
|
+
const dna=s.content?.styleDna||'';
|
|
215
|
+
const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+dna;
|
|
216
|
+
console.log(JSON.stringify({brand_style_requirement:block}));
|
|
217
|
+
"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Replace `CHOSEN_STYLE_NAME` with the style name the user selected, and `LANG` with the language code.
|
|
221
|
+
|
|
222
|
+
Pass the output JSON directly as the `--ext` value:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
--ext "$(node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language LANG --json | node -e "
|
|
226
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
227
|
+
const j=JSON.parse(d);
|
|
228
|
+
const s=(j.list||[]).find(s=>s.name==='CHOSEN_STYLE_NAME');
|
|
229
|
+
const labels=(s.content?.labels?.LANG||s.content?.labels?.en||[]).join(', ');
|
|
230
|
+
const dna=s.content?.styleDna||'';
|
|
231
|
+
const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+dna;
|
|
232
|
+
console.log(JSON.stringify({brand_style_requirement:block}));
|
|
233
|
+
")"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
If the user chose "no preference" (option 0), do NOT pass `--ext`.
|
|
136
237
|
|
|
137
238
|
#### Step 2: Obtain live_doc_id
|
|
138
239
|
|
|
@@ -142,20 +243,35 @@ Follow Constraint #4. If Mode 1 was already run, reuse the same `live_doc_id`.
|
|
|
142
243
|
|
|
143
244
|
| Condition | Mode | What to pass |
|
|
144
245
|
|-----------|------|--------------|
|
|
145
|
-
| No `thread_short_id` in this session (truly first call) | New conversation | `--live-doc-id` + `--skill-id twitter-writer` |
|
|
146
|
-
| `thread_short_id` already exists in this session (all subsequent inputs, including after Mode 1) | Follow-up | `--thread-id` + `--live-doc-id` |
|
|
147
|
-
| User says "new topic" / "start over" (clear `thread_short_id`) | New conversation | `--live-doc-id` + `--skill-id twitter-writer` |
|
|
246
|
+
| No `thread_short_id` in this session (truly first call) | New conversation | `--live-doc-id` + `--skill-id twitter-writer` + `--ext` (if style chosen) |
|
|
247
|
+
| `thread_short_id` already exists in this session (all subsequent inputs, including after Mode 1) | Follow-up | `--thread-id` + `--live-doc-id` (NO `--ext`) |
|
|
248
|
+
| User says "new topic" / "start over" (clear `thread_short_id`) | New conversation | `--live-doc-id` + `--skill-id twitter-writer` + `--ext` (repeat Step 1.5) |
|
|
148
249
|
|
|
149
250
|
#### Step 4: Call SuperAgent
|
|
150
251
|
|
|
151
|
-
|
|
252
|
+
Replace `LANG` with the user's language: `en`, `zh`, `ja`, `ko`. See Constraint #6.
|
|
253
|
+
|
|
254
|
+
**New conversation without style (no `thread_short_id`, user chose no preference or list was empty):**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
node felo-superAgent/scripts/run_superagent.mjs \
|
|
258
|
+
--query "/twitter-writer ENRICHED_QUERY" \
|
|
259
|
+
--live-doc-id "LIVE_DOC_ID" \
|
|
260
|
+
--skill-id twitter-writer \
|
|
261
|
+
--accept-language LANG \
|
|
262
|
+
--json
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**New conversation with brand style:**
|
|
152
266
|
|
|
153
267
|
```bash
|
|
154
268
|
node felo-superAgent/scripts/run_superagent.mjs \
|
|
155
269
|
--query "/twitter-writer ENRICHED_QUERY" \
|
|
156
270
|
--live-doc-id "LIVE_DOC_ID" \
|
|
157
271
|
--skill-id twitter-writer \
|
|
158
|
-
--
|
|
272
|
+
--ext '{"brand_style_requirement":"Style name: darioamodei\nStyle labels: Thoughtful long-form essays\nStyle DNA: # Dario Amodei (@DarioAmodei) Tweet Writing Style DNA\n\n## 风格速写\nDario writes like a serious intellectual...(full content, do NOT truncate)"}' \
|
|
273
|
+
--accept-language LANG \
|
|
274
|
+
--json
|
|
159
275
|
```
|
|
160
276
|
|
|
161
277
|
**Follow-up (`thread_short_id` already exists in session):**
|
|
@@ -165,7 +281,8 @@ node felo-superAgent/scripts/run_superagent.mjs \
|
|
|
165
281
|
--query "/twitter-writer USER_FOLLOW_UP" \
|
|
166
282
|
--thread-id "THREAD_SHORT_ID" \
|
|
167
283
|
--live-doc-id "LIVE_DOC_ID" \
|
|
168
|
-
--accept-language LANG
|
|
284
|
+
--accept-language LANG \
|
|
285
|
+
--json
|
|
169
286
|
```
|
|
170
287
|
|
|
171
288
|
**Query construction guidelines:**
|
|
@@ -186,7 +303,7 @@ node felo-superAgent/scripts/run_superagent.mjs \
|
|
|
186
303
|
|
|
187
304
|
#### Step 5: Save state
|
|
188
305
|
|
|
189
|
-
Extract `thread_short_id` and `live_doc_short_id` from
|
|
306
|
+
Extract `thread_short_id` and `live_doc_short_id` from the JSON response fields `data.thread_short_id` and `data.live_doc_short_id`.
|
|
190
307
|
|
|
191
308
|
---
|
|
192
309
|
|
|
@@ -197,17 +314,18 @@ User input
|
|
|
197
314
|
│
|
|
198
315
|
├── Contains account name + "analyze / style / DNA / how does X write"
|
|
199
316
|
│ OR: アカウント名 + "分析 / スタイル / DNA / どう書いている"
|
|
200
|
-
│ → Mode 1 (Style DNA Extraction)
|
|
317
|
+
│ → Mode 1 (Style DNA Extraction) — skip style library step
|
|
201
318
|
│
|
|
202
319
|
├── Contains account name + "write / create / imitate / in the style of"
|
|
203
320
|
│ OR: アカウント名 + "書いて / 作って / 風に / 真似て"
|
|
204
|
-
│ → Mode 1 first → then Mode 2 (
|
|
321
|
+
│ → Mode 1 first → then Mode 2 (follow-up, skip style library step)
|
|
205
322
|
│
|
|
206
323
|
├── Contains topic + "write / draft / tweet / thread / X post"
|
|
207
324
|
│ OR: トピック + "書いて / ツイート / スレッド / Xの投稿"
|
|
208
325
|
│ → Mode 2 directly
|
|
209
|
-
│ └──
|
|
210
|
-
│
|
|
326
|
+
│ └── New conversation?
|
|
327
|
+
│ YES → Step 1.5: fetch TWITTER styles, present to user, wait for choice
|
|
328
|
+
│ NO → follow-up, skip style library step
|
|
211
329
|
│
|
|
212
330
|
└── Ambiguous (e.g., "help me with tweets" / "ツイートを手伝って")
|
|
213
331
|
→ Ask user: do they want to analyze an account's style, or create content?
|
|
@@ -231,81 +349,197 @@ node felo-x-search/scripts/run_x_search.mjs --id "paulg" --user
|
|
|
231
349
|
|
|
232
350
|
**Step 2:** Get `live_doc_id` (list or create)
|
|
233
351
|
|
|
234
|
-
**Step 3:** Call SuperAgent (
|
|
352
|
+
**Step 3:** Call SuperAgent (Mode 1 — no style library step):
|
|
235
353
|
```bash
|
|
236
354
|
node felo-superAgent/scripts/run_superagent.mjs \
|
|
237
355
|
--query "/twitter-writer @paulg のツイートを分析し、文体のスタイルDNAドキュメントを作成してください。トーン、文章構造、冒頭フック、締めのアクション、頻出ワード、ハッシュタグ戦略、絵文字の使い方などを含めてください。\n\nアカウント概要:[BIO]\n\nツイート:\n[TWEETS]" \
|
|
238
356
|
--live-doc-id "LIVE_DOC_ID" \
|
|
239
357
|
--skill-id twitter-writer \
|
|
240
|
-
--accept-language ja
|
|
358
|
+
--accept-language ja \
|
|
359
|
+
--json
|
|
241
360
|
```
|
|
242
361
|
|
|
243
|
-
**Step 4:** Save `thread_short_id` and `live_doc_short_id` from
|
|
362
|
+
**Step 4:** Save `thread_short_id` and `live_doc_short_id` from JSON response fields `data.thread_short_id` and `data.live_doc_short_id`.
|
|
244
363
|
|
|
245
364
|
---
|
|
246
365
|
|
|
247
|
-
### Example B: Create tweets with a reference style
|
|
366
|
+
### Example B: Create tweets with a reference style (Mode 1 → Mode 2)
|
|
248
367
|
|
|
249
368
|
```
|
|
250
369
|
User: "@paulg のスタイルでスタートアップについてのツイートを3つ書いて"
|
|
251
370
|
```
|
|
252
371
|
|
|
253
|
-
**Step 1:** Run Mode 1 to extract style DNA (same as Example A)
|
|
372
|
+
**Step 1:** Run Mode 1 to extract style DNA (same as Example A). Style library step is skipped because Mode 1 already establishes style context in the thread.
|
|
254
373
|
|
|
255
|
-
**Step 2:** Reuse `live_doc_id` from Mode 1
|
|
374
|
+
**Step 2:** Reuse `live_doc_id` from Mode 1.
|
|
256
375
|
|
|
257
|
-
**Step 3:** Follow-up call (continuing the same thread — `thread_short_id` from
|
|
376
|
+
**Step 3:** Follow-up call (continuing the same thread — `thread_short_id` from Mode 1, no `--ext`):
|
|
258
377
|
```bash
|
|
259
378
|
node felo-superAgent/scripts/run_superagent.mjs \
|
|
260
379
|
--query "/twitter-writer 上記で抽出した @paulg のスタイルDNAをもとに、「スタートアップ」をテーマにしたツイートを3パターン作成してください。それぞれ異なるトーンや切り口で、280文字以内に収めてください。" \
|
|
261
380
|
--thread-id "THREAD_SHORT_ID" \
|
|
262
381
|
--live-doc-id "LIVE_DOC_ID" \
|
|
263
|
-
--accept-language ja
|
|
382
|
+
--accept-language ja \
|
|
383
|
+
--json
|
|
264
384
|
```
|
|
265
385
|
|
|
266
386
|
---
|
|
267
387
|
|
|
268
|
-
### Example C: Write a thread directly
|
|
388
|
+
### Example C: Write a thread directly (Mode 2, new conversation, with style selection)
|
|
269
389
|
|
|
270
390
|
```
|
|
271
391
|
User: "Write a Twitter thread about why most startups fail"
|
|
272
392
|
```
|
|
273
393
|
|
|
274
|
-
**Step 1:**
|
|
394
|
+
**Step 1.5:** New conversation, no existing thread → fetch TWITTER styles (names only):
|
|
395
|
+
```bash
|
|
396
|
+
node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
|
|
397
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
398
|
+
const j=JSON.parse(d);
|
|
399
|
+
const list=j.list||[];
|
|
400
|
+
const user=list.filter(s=>!s.recommended);
|
|
401
|
+
const rec=list.filter(s=>s.recommended);
|
|
402
|
+
if(user.length){console.log('[Your styles]');user.forEach((s,i)=>{const labels=(s.content?.labels?.en||[]).join(', ');console.log((i+1)+'. '+s.name+(labels?' — '+labels:''));});}
|
|
403
|
+
if(rec.length){console.log('[Recommended styles]');rec.forEach((s,i)=>{const labels=(s.content?.labels?.en||[]).join(', ');console.log((user.length+i+1)+'. '+s.name+(labels?' — '+labels:''));});}
|
|
404
|
+
if(!list.length)console.log('(No styles found)');
|
|
405
|
+
"
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Present to user:
|
|
409
|
+
```
|
|
410
|
+
Here are the available Twitter writing styles — choosing one will make the output more accurate:
|
|
411
|
+
|
|
412
|
+
[Your styles]
|
|
413
|
+
1. My Bold Voice
|
|
275
414
|
|
|
276
|
-
|
|
415
|
+
[Recommended styles]
|
|
416
|
+
2. darioamodei
|
|
277
417
|
|
|
278
|
-
|
|
418
|
+
0. No preference — use default style
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
User replies: `1`
|
|
422
|
+
|
|
423
|
+
**Step 2:** Get `live_doc_id`.
|
|
424
|
+
|
|
425
|
+
**Step 3:** New conversation with chosen style — extract full Style DNA via `--json` and pass as `--ext`:
|
|
279
426
|
```bash
|
|
280
427
|
node felo-superAgent/scripts/run_superagent.mjs \
|
|
281
428
|
--query "/twitter-writer Write a Twitter thread (6–8 tweets) about why most startups fail. Start with a strong hook tweet that grabs attention. Each tweet should stand alone but flow naturally into the next." \
|
|
282
429
|
--live-doc-id "LIVE_DOC_ID" \
|
|
283
430
|
--skill-id twitter-writer \
|
|
284
|
-
--accept-language en
|
|
431
|
+
--ext "$(node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
|
|
432
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
433
|
+
const j=JSON.parse(d);
|
|
434
|
+
const s=(j.list||[]).find(s=>s.name==='My Bold Voice');
|
|
435
|
+
const labels=(s.content?.labels?.en||[]).join(', ');
|
|
436
|
+
const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+s.content.styleDna;
|
|
437
|
+
console.log(JSON.stringify({brand_style_requirement:block}));
|
|
438
|
+
")" \
|
|
439
|
+
--accept-language en \
|
|
440
|
+
--json
|
|
285
441
|
```
|
|
286
442
|
|
|
443
|
+
**Step 4:** Save `thread_short_id` and `live_doc_short_id` from JSON response fields `data.thread_short_id` and `data.live_doc_short_id`.
|
|
444
|
+
|
|
287
445
|
---
|
|
288
446
|
|
|
289
|
-
### Example D: Iterate on generated content
|
|
447
|
+
### Example D: Iterate on generated content (follow-up, no style step)
|
|
290
448
|
|
|
291
449
|
```
|
|
292
450
|
User: "2番目のツイートをもっとユーモラスにして、絵文字も追加して"
|
|
293
451
|
```
|
|
294
452
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
**Step 2:** This is a follow-up — pass `--thread-id` with the saved `thread_short_id`.
|
|
453
|
+
Already have `thread_short_id` and `live_doc_id` from the previous call. This is a follow-up — do NOT fetch styles again, do NOT pass `--ext`.
|
|
298
454
|
|
|
299
|
-
**Step 3:** Follow-up call (`thread_short_id` already exists in session):
|
|
300
455
|
```bash
|
|
301
456
|
node felo-superAgent/scripts/run_superagent.mjs \
|
|
302
457
|
--query "/twitter-writer 上記で生成した2番目のツイートを修正してください。トーンをよりユーモラスで軽快にし、適切な絵文字を追加してください。内容の意図は変えないでください。" \
|
|
303
458
|
--thread-id "THREAD_SHORT_ID" \
|
|
304
459
|
--live-doc-id "LIVE_DOC_ID" \
|
|
305
|
-
--accept-language ja
|
|
460
|
+
--accept-language ja \
|
|
461
|
+
--json
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Save** updated `thread_short_id` and `live_doc_short_id` from JSON response fields `data.thread_short_id` and `data.live_doc_short_id`.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
### Example E: User specifies style by name
|
|
469
|
+
|
|
470
|
+
```
|
|
471
|
+
User: "Write a tweet about AI trends using my 'Bold Voice' style"
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Step 1.5a:** User already named the style → extract full Style DNA via `--json`. No need to ask the user again.
|
|
475
|
+
|
|
476
|
+
**Step 3:** New conversation with that style:
|
|
477
|
+
```bash
|
|
478
|
+
node felo-superAgent/scripts/run_superagent.mjs \
|
|
479
|
+
--query "/twitter-writer Write a tweet about AI trends" \
|
|
480
|
+
--live-doc-id "LIVE_DOC_ID" \
|
|
481
|
+
--skill-id twitter-writer \
|
|
482
|
+
--ext "$(node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
|
|
483
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
484
|
+
const j=JSON.parse(d);
|
|
485
|
+
const s=(j.list||[]).find(s=>s.name==='My Bold Voice');
|
|
486
|
+
const labels=(s.content?.labels?.en||[]).join(', ');
|
|
487
|
+
const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+s.content.styleDna;
|
|
488
|
+
console.log(JSON.stringify({brand_style_requirement:block}));
|
|
489
|
+
")" \
|
|
490
|
+
--accept-language en \
|
|
491
|
+
--json
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
### Example F: Style library is empty
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
User: "Write a tweet about the new product launch"
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Step 1.5b:** Fetch styles (names only):
|
|
503
|
+
```bash
|
|
504
|
+
node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
|
|
505
|
+
const d=require('fs').readFileSync('/dev/stdin','utf8');
|
|
506
|
+
const j=JSON.parse(d);
|
|
507
|
+
if(!(j.list||[]).length)console.log('(No styles found)');
|
|
508
|
+
else console.log((j.list||[]).map(s=>s.name).join('\n'));
|
|
509
|
+
"
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Output: `(No styles found)`
|
|
513
|
+
|
|
514
|
+
**Skip silently.** Proceed directly without `--ext`:
|
|
515
|
+
```bash
|
|
516
|
+
node felo-superAgent/scripts/run_superagent.mjs \
|
|
517
|
+
--query "/twitter-writer Write a tweet about the new product launch. Provide 3 versions with different tones." \
|
|
518
|
+
--live-doc-id "LIVE_DOC_ID" \
|
|
519
|
+
--skill-id twitter-writer \
|
|
520
|
+
--accept-language en \
|
|
521
|
+
--json
|
|
306
522
|
```
|
|
307
523
|
|
|
308
|
-
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
### Example G: User chooses no preference
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
User: "帮我写一条关于新产品发布的推文"
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Step 1.5:** Fetch styles, present list. User replies: `0` (no preference).
|
|
533
|
+
|
|
534
|
+
Proceed without `--ext`:
|
|
535
|
+
```bash
|
|
536
|
+
node felo-superAgent/scripts/run_superagent.mjs \
|
|
537
|
+
--query "/twitter-writer 帮我写3条关于新产品发布的推文,每条风格略有不同。" \
|
|
538
|
+
--live-doc-id "LIVE_DOC_ID" \
|
|
539
|
+
--skill-id twitter-writer \
|
|
540
|
+
--accept-language zh \
|
|
541
|
+
--json
|
|
542
|
+
```
|
|
309
543
|
|
|
310
544
|
---
|
|
311
545
|
|
|
@@ -316,13 +550,14 @@ node felo-superAgent/scripts/run_superagent.mjs \
|
|
|
316
550
|
| Account not found or no tweets returned | Inform user, suggest trying a different username or providing tweet samples manually |
|
|
317
551
|
| `FELO_API_KEY` not set | Stop and show setup instructions (same as `felo-superAgent` SKILL.md) |
|
|
318
552
|
| SuperAgent call fails | Check `live_doc_id` validity; retry once with the same parameters |
|
|
319
|
-
|
|
|
553
|
+
| Style library fetch fails | Log warning to stderr, skip silently, proceed without `--ext` |
|
|
554
|
+
| User asks for Mode 2 with no style DNA and no account | Proceed to Step 1.5 (style library selection) |
|
|
320
555
|
| User explicitly requests a new canvas | Create a new LiveDoc: `node felo-livedoc/scripts/run_livedoc.mjs create --name "Twitter Writer" --json` |
|
|
321
556
|
| Tweet content too long for query (>2000 chars) | Trim to the 10–15 most representative tweets; prioritize high-engagement ones |
|
|
322
557
|
|
|
323
558
|
## References
|
|
324
559
|
|
|
325
|
-
- [felo-superAgent SKILL.md](../felo-superAgent/SKILL.md) — SuperAgent calling conventions and
|
|
560
|
+
- [felo-superAgent SKILL.md](../felo-superAgent/SKILL.md) — SuperAgent calling conventions, `--ext` format, and style library script
|
|
326
561
|
- [felo-x-search SKILL.md](../felo-x-search/SKILL.md) — X/Twitter search commands
|
|
327
562
|
- [felo-livedoc SKILL.md](../felo-livedoc/SKILL.md) — LiveDoc management commands
|
|
328
563
|
- [Felo Open Platform](https://openapi.felo.ai/docs/)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "felo-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.48",
|
|
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",
|
package/src/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createRequire } from "module";
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { search } from "./search.js";
|
|
6
6
|
import { slides, listPptThemes } from "./slides.js";
|
|
7
|
-
import { superAgent, listLiveDocs, listLiveDocResources } from "./superAgent.js";
|
|
7
|
+
import { superAgent, listLiveDocs, listLiveDocResources, listStyleLibrary } from "./superAgent.js";
|
|
8
8
|
import { appleBuyAdvisor } from "./appleBuyAdvisor.js";
|
|
9
9
|
import { webFetch } from "./webFetch.js";
|
|
10
10
|
import { youtubeSubtitling } from "./youtubeSubtitling.js";
|
|
@@ -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")
|
|
@@ -1030,4 +1049,22 @@ program
|
|
|
1030
1049
|
flushStdioThenExit(1);
|
|
1031
1050
|
});
|
|
1032
1051
|
|
|
1052
|
+
program
|
|
1053
|
+
.command("style-library")
|
|
1054
|
+
.description("List style library entries for a given category (TWITTER, INSTAGRAM, LEMON8, NOTECOM, WEBSITE, IMAGE)")
|
|
1055
|
+
.argument("<category>", "style category (e.g. TWITTER)")
|
|
1056
|
+
.option("-j, --json", "output raw JSON")
|
|
1057
|
+
.option("--accept-language <lang>", "language for labels/tags (e.g. en, zh-Hans, ja)", "en")
|
|
1058
|
+
.option("-t, --timeout <seconds>", "request timeout in seconds", "60")
|
|
1059
|
+
.action(async (category, opts) => {
|
|
1060
|
+
const timeoutMs = parseInt(opts.timeout, 10) * 1000;
|
|
1061
|
+
const code = await listStyleLibrary(category, {
|
|
1062
|
+
json: opts.json,
|
|
1063
|
+
acceptLanguage: opts.acceptLanguage || 'en',
|
|
1064
|
+
timeoutMs: Number.isNaN(timeoutMs) ? 60000 : timeoutMs,
|
|
1065
|
+
});
|
|
1066
|
+
process.exitCode = code;
|
|
1067
|
+
flushStdioThenExit(code);
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1033
1070
|
program.parse();
|
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
|
|