felo-ai 0.2.47 → 0.2.49

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.
@@ -11,21 +11,29 @@ 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. **NEVER use `--json` flag** when calling SuperAgent. The script MUST run in default streaming mode. State IDs are extracted from the `[state]` line in stderr.
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. **NEVER summarize or re-output SuperAgent's stdout.** The answer is already streamed directly to the user. Only add supplementary info (LiveDoc URL) if needed.
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
- 4. **`--live-doc-id` is REQUIRED** for every SuperAgent call. Follow the livedoc reuse rules from `felo-superAgent/SKILL.md`:
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`
21
- - If list is empty: run `node felo-livedoc/scripts/run_livedoc.mjs create --name "Twitter Writer" --json`, use `data.short_id`
18
+ 4. **`--live-doc-id` is REQUIRED** for every SuperAgent call. Follow these rules strictly:
19
+ - Reuse any `live_doc_id` already available in this session (from a prior SuperAgent or livedoc call)
20
+ - If none: run `node felo-livedoc/scripts/run_livedoc.mjs list --json`, then find the **first item where `is_shared === false`** in `data.items` (list is sorted by modification time descending, so this gives the most recently modified private LiveDoc). Use its `short_id`.
21
+ - If no `is_shared === false` item exists (or list is empty): run `node felo-livedoc/scripts/run_livedoc.mjs create --name "Twitter Writer" --json`, use `data.short_id`
22
+ - **NEVER use a LiveDoc where `is_shared === true`** — shared LiveDocs belong to other projects and will cause a 502 error.
22
23
 
23
- 5. **Always persist state.** After every SuperAgent call, extract `thread_short_id` and `live_doc_short_id` from the stderr `[state]` line. Use them in subsequent calls.
24
+ 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
25
 
25
26
  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
27
 
27
28
  7. **Do NOT pass `--timeout` to the SuperAgent script.** The script manages its own connection lifecycle.
28
29
 
30
+ 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.
31
+
32
+ - Style category is always `TWITTER` (hardcoded — this skill only writes tweets).
33
+ - `--ext` is only valid for new conversations. Never pass it in follow-up mode (`--thread-id`).
34
+ - If the style library returns no entries, skip silently and proceed without `--ext`.
35
+ - Mode 1 (style DNA extraction) does NOT use this step.
36
+
29
37
  ## When to Use
30
38
 
31
39
  Trigger this skill when the user wants to:
@@ -82,6 +90,8 @@ Determine conversation mode first:
82
90
  - If **no** `thread_short_id` exists in this session → new conversation (pass `--skill-id twitter-writer`)
83
91
  - If `thread_short_id` **already exists** in this session → follow-up (pass `--thread-id`)
84
92
 
93
+ Replace `LANG` with the user's language: `en` (English), `zh` (Chinese), `ja` (Japanese), `ko` (Korean). See Constraint #6.
94
+
85
95
  **New conversation (first call in session):**
86
96
 
87
97
  ```bash
@@ -89,7 +99,8 @@ node felo-superAgent/scripts/run_superagent.mjs \
89
99
  --query "/twitter-writer ENRICHED_QUERY_WITH_TWEET_CONTENT" \
90
100
  --live-doc-id "LIVE_DOC_ID" \
91
101
  --skill-id twitter-writer \
92
- --accept-language LANG
102
+ --accept-language LANG \
103
+ --json
93
104
  ```
94
105
 
95
106
  **Follow-up (thread_short_id already exists):**
@@ -99,7 +110,8 @@ node felo-superAgent/scripts/run_superagent.mjs \
99
110
  --query "/twitter-writer ENRICHED_QUERY_WITH_TWEET_CONTENT" \
100
111
  --thread-id "THREAD_SHORT_ID" \
101
112
  --live-doc-id "LIVE_DOC_ID" \
102
- --accept-language LANG
113
+ --accept-language LANG \
114
+ --json
103
115
  ```
104
116
 
105
117
  **Query construction example:**
@@ -117,7 +129,7 @@ Keep the query under 2000 characters. If tweet content is too long, include the
117
129
 
118
130
  #### Step 4: Save state
119
131
 
120
- Extract `thread_short_id` and `live_doc_short_id` from stderr `[state]` line. Save for follow-up calls.
132
+ 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
133
 
122
134
  ---
123
135
 
@@ -132,7 +144,98 @@ Extract `thread_short_id` and `live_doc_short_id` from stderr `[state]` line. Sa
132
144
  - If Mode 1 was just run in this session → style DNA is already in the LiveDoc canvas, use follow-up mode
133
145
  - If user provides a style DNA directly → include it in the query
134
146
  - If user provides an account name → run Mode 1 first, then continue with Mode 2
135
- - If no style DNA → proceed with general tweet writing (SuperAgent will use its default twitter-writer behavior)
147
+ - If no style DNA → proceed to style library selection (Step 1.5)
148
+
149
+ #### Step 1.5: Brand Style Selection (new conversations only)
150
+
151
+ **Only run this step when:**
152
+ - This is a **new conversation** (no `thread_short_id` in session), AND
153
+ - Mode 1 was NOT just run (i.e., there is no existing thread carrying style DNA context)
154
+
155
+ **Skip this step entirely when:**
156
+ - `thread_short_id` already exists (follow-up) — `--ext` has no effect in follow-up mode
157
+ - Mode 1 was just run in this session — style context is already in the thread
158
+
159
+ **1.5a. Check if user already specified a style:**
160
+
161
+ 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.
162
+
163
+ **1.5b. Fetch the TWITTER style library (names only):**
164
+
165
+ 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.
166
+
167
+ ```bash
168
+ node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language LANG --json | node -e "
169
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
170
+ const j=JSON.parse(d);
171
+ const list=j.list||[];
172
+ const user=list.filter(s=>!s.recommended);
173
+ const rec=list.filter(s=>s.recommended);
174
+ 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:'')); }); }
175
+ 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:'')); }); }
176
+ if(!list.length) console.log('(No styles found)');
177
+ "
178
+ ```
179
+
180
+ 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.
181
+
182
+ Always pass `--accept-language` matching the user's language (same value used for SuperAgent).
183
+
184
+ **1.5c. Handle the result:**
185
+
186
+ **If the list is empty:** Skip silently. Proceed to Step 2 without `--ext`.
187
+
188
+ **If styles are available:** Output the COMPLETE list as plain text — every style returned by the API, grouped by type, numbered sequentially. NEVER use `AskUserQuestion` tool (it limits to 4 options and will silently drop styles). NEVER pre-select or filter styles on behalf of the user. Always append a "no preference" option as the last item. Then wait for the user's plain-text reply before proceeding.
189
+
190
+ Example presentation (adapt language to match user's language):
191
+ ```
192
+ 以下是可用的推文写作风格,选一个会让输出更贴合你的需求:
193
+
194
+ [你的风格]
195
+ 1. My Bold Voice
196
+
197
+ [推荐风格]
198
+ 2. elonmusk — Shitposting provocateur
199
+ 3. naval — Pithy aphorism master
200
+ ...(所有风格全部列出,不省略)
201
+
202
+ 0. 无偏好,使用默认风格
203
+ ```
204
+
205
+ **1.5d. Build `--ext` from the chosen style:**
206
+
207
+ 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.
208
+
209
+ ```bash
210
+ node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language LANG --json | node -e "
211
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
212
+ const j=JSON.parse(d);
213
+ const s=(j.list||[]).find(s=>s.name==='CHOSEN_STYLE_NAME');
214
+ if(!s){process.stderr.write('Style not found\n');process.exit(1);}
215
+ const labels=(s.content?.labels?.LANG||s.content?.labels?.en||[]).join(', ');
216
+ const dna=s.content?.styleDna||'';
217
+ const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+dna;
218
+ console.log(JSON.stringify({brand_style_requirement:block}));
219
+ "
220
+ ```
221
+
222
+ Replace `CHOSEN_STYLE_NAME` with the style name the user selected, and `LANG` with the language code.
223
+
224
+ Pass the output JSON directly as the `--ext` value:
225
+
226
+ ```bash
227
+ --ext "$(node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language LANG --json | node -e "
228
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
229
+ const j=JSON.parse(d);
230
+ const s=(j.list||[]).find(s=>s.name==='CHOSEN_STYLE_NAME');
231
+ const labels=(s.content?.labels?.LANG||s.content?.labels?.en||[]).join(', ');
232
+ const dna=s.content?.styleDna||'';
233
+ const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+dna;
234
+ console.log(JSON.stringify({brand_style_requirement:block}));
235
+ ")"
236
+ ```
237
+
238
+ If the user chose "no preference" (option 0), do NOT pass `--ext`.
136
239
 
137
240
  #### Step 2: Obtain live_doc_id
138
241
 
@@ -142,20 +245,35 @@ Follow Constraint #4. If Mode 1 was already run, reuse the same `live_doc_id`.
142
245
 
143
246
  | Condition | Mode | What to pass |
144
247
  |-----------|------|--------------|
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` |
248
+ | No `thread_short_id` in this session (truly first call) | New conversation | `--live-doc-id` + `--skill-id twitter-writer` + `--ext` (if style chosen) |
249
+ | `thread_short_id` already exists in this session (all subsequent inputs, including after Mode 1) | Follow-up | `--thread-id` + `--live-doc-id` (NO `--ext`) |
250
+ | 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
251
 
149
252
  #### Step 4: Call SuperAgent
150
253
 
151
- **New conversation (no `thread_short_id` in session):**
254
+ Replace `LANG` with the user's language: `en`, `zh`, `ja`, `ko`. See Constraint #6.
255
+
256
+ **New conversation without style (no `thread_short_id`, user chose no preference or list was empty):**
257
+
258
+ ```bash
259
+ node felo-superAgent/scripts/run_superagent.mjs \
260
+ --query "/twitter-writer ENRICHED_QUERY" \
261
+ --live-doc-id "LIVE_DOC_ID" \
262
+ --skill-id twitter-writer \
263
+ --accept-language LANG \
264
+ --json
265
+ ```
266
+
267
+ **New conversation with brand style:**
152
268
 
153
269
  ```bash
154
270
  node felo-superAgent/scripts/run_superagent.mjs \
155
271
  --query "/twitter-writer ENRICHED_QUERY" \
156
272
  --live-doc-id "LIVE_DOC_ID" \
157
273
  --skill-id twitter-writer \
158
- --accept-language LANG
274
+ --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)"}' \
275
+ --accept-language LANG \
276
+ --json
159
277
  ```
160
278
 
161
279
  **Follow-up (`thread_short_id` already exists in session):**
@@ -165,7 +283,8 @@ node felo-superAgent/scripts/run_superagent.mjs \
165
283
  --query "/twitter-writer USER_FOLLOW_UP" \
166
284
  --thread-id "THREAD_SHORT_ID" \
167
285
  --live-doc-id "LIVE_DOC_ID" \
168
- --accept-language LANG
286
+ --accept-language LANG \
287
+ --json
169
288
  ```
170
289
 
171
290
  **Query construction guidelines:**
@@ -186,7 +305,7 @@ node felo-superAgent/scripts/run_superagent.mjs \
186
305
 
187
306
  #### Step 5: Save state
188
307
 
189
- Extract `thread_short_id` and `live_doc_short_id` from stderr `[state]` line.
308
+ 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
309
 
191
310
  ---
192
311
 
@@ -197,17 +316,18 @@ User input
197
316
 
198
317
  ├── Contains account name + "analyze / style / DNA / how does X write"
199
318
  │ OR: アカウント名 + "分析 / スタイル / DNA / どう書いている"
200
- │ → Mode 1 (Style DNA Extraction)
319
+ │ → Mode 1 (Style DNA Extraction) — skip style library step
201
320
 
202
321
  ├── Contains account name + "write / create / imitate / in the style of"
203
322
  │ OR: アカウント名 + "書いて / 作って / 風に / 真似て"
204
- │ → Mode 1 first → then Mode 2 (Creation)
323
+ │ → Mode 1 first → then Mode 2 (follow-up, skip style library step)
205
324
 
206
325
  ├── Contains topic + "write / draft / tweet / thread / X post"
207
326
  │ OR: トピック + "書いて / ツイート / スレッド / Xの投稿"
208
327
  │ → Mode 2 directly
209
- │ └── If no style DNA available: ask user if they want to provide
210
- a reference account, or proceed with general twitter-writer style
328
+ │ └── New conversation?
329
+ YES Step 1.5: fetch TWITTER styles, present to user, wait for choice
330
+ │ NO → follow-up, skip style library step
211
331
 
212
332
  └── Ambiguous (e.g., "help me with tweets" / "ツイートを手伝って")
213
333
  → Ask user: do they want to analyze an account's style, or create content?
@@ -231,81 +351,197 @@ node felo-x-search/scripts/run_x_search.mjs --id "paulg" --user
231
351
 
232
352
  **Step 2:** Get `live_doc_id` (list or create)
233
353
 
234
- **Step 3:** Call SuperAgent (first call in session — no `thread_short_id` yet):
354
+ **Step 3:** Call SuperAgent (Mode 1 — no style library step):
235
355
  ```bash
236
356
  node felo-superAgent/scripts/run_superagent.mjs \
237
357
  --query "/twitter-writer @paulg のツイートを分析し、文体のスタイルDNAドキュメントを作成してください。トーン、文章構造、冒頭フック、締めのアクション、頻出ワード、ハッシュタグ戦略、絵文字の使い方などを含めてください。\n\nアカウント概要:[BIO]\n\nツイート:\n[TWEETS]" \
238
358
  --live-doc-id "LIVE_DOC_ID" \
239
359
  --skill-id twitter-writer \
240
- --accept-language ja
360
+ --accept-language ja \
361
+ --json
241
362
  ```
242
363
 
243
- **Step 4:** Save `thread_short_id` and `live_doc_short_id` from stderr `[state]`.
364
+ **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
365
 
245
366
  ---
246
367
 
247
- ### Example B: Create tweets with a reference style
368
+ ### Example B: Create tweets with a reference style (Mode 1 → Mode 2)
248
369
 
249
370
  ```
250
371
  User: "@paulg のスタイルでスタートアップについてのツイートを3つ書いて"
251
372
  ```
252
373
 
253
- **Step 1:** Run Mode 1 to extract style DNA (same as Example A)
374
+ **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
375
 
255
- **Step 2:** Reuse `live_doc_id` from Mode 1
376
+ **Step 2:** Reuse `live_doc_id` from Mode 1.
256
377
 
257
- **Step 3:** Follow-up call (continuing the same thread — `thread_short_id` from Example A):
378
+ **Step 3:** Follow-up call (continuing the same thread — `thread_short_id` from Mode 1, no `--ext`):
258
379
  ```bash
259
380
  node felo-superAgent/scripts/run_superagent.mjs \
260
381
  --query "/twitter-writer 上記で抽出した @paulg のスタイルDNAをもとに、「スタートアップ」をテーマにしたツイートを3パターン作成してください。それぞれ異なるトーンや切り口で、280文字以内に収めてください。" \
261
382
  --thread-id "THREAD_SHORT_ID" \
262
383
  --live-doc-id "LIVE_DOC_ID" \
263
- --accept-language ja
384
+ --accept-language ja \
385
+ --json
264
386
  ```
265
387
 
266
388
  ---
267
389
 
268
- ### Example C: Write a thread directly
390
+ ### Example C: Write a thread directly (Mode 2, new conversation, with style selection)
269
391
 
270
392
  ```
271
393
  User: "Write a Twitter thread about why most startups fail"
272
394
  ```
273
395
 
274
- **Step 1:** No style DNA needed, proceed directly
396
+ **Step 1.5:** New conversation, no existing thread → fetch TWITTER styles (names only):
397
+ ```bash
398
+ node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
399
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
400
+ const j=JSON.parse(d);
401
+ const list=j.list||[];
402
+ const user=list.filter(s=>!s.recommended);
403
+ const rec=list.filter(s=>s.recommended);
404
+ 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:''));});}
405
+ 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:''));});}
406
+ if(!list.length)console.log('(No styles found)');
407
+ "
408
+ ```
409
+
410
+ Present to user:
411
+ ```
412
+ Here are the available Twitter writing styles — choosing one will make the output more accurate:
413
+
414
+ [Your styles]
415
+ 1. My Bold Voice
275
416
 
276
- **Step 2:** Get `live_doc_id`
417
+ [Recommended styles]
418
+ 2. darioamodei
277
419
 
278
- **Step 3:** New conversation with `twitter-writer` (first call in session no `thread_short_id` yet):
420
+ 0. No preferenceuse default style
421
+ ```
422
+
423
+ User replies: `1`
424
+
425
+ **Step 2:** Get `live_doc_id`.
426
+
427
+ **Step 3:** New conversation with chosen style — extract full Style DNA via `--json` and pass as `--ext`:
279
428
  ```bash
280
429
  node felo-superAgent/scripts/run_superagent.mjs \
281
430
  --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
431
  --live-doc-id "LIVE_DOC_ID" \
283
432
  --skill-id twitter-writer \
284
- --accept-language en
433
+ --ext "$(node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
434
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
435
+ const j=JSON.parse(d);
436
+ const s=(j.list||[]).find(s=>s.name==='My Bold Voice');
437
+ const labels=(s.content?.labels?.en||[]).join(', ');
438
+ const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+s.content.styleDna;
439
+ console.log(JSON.stringify({brand_style_requirement:block}));
440
+ ")" \
441
+ --accept-language en \
442
+ --json
285
443
  ```
286
444
 
445
+ **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`.
446
+
287
447
  ---
288
448
 
289
- ### Example D: Iterate on generated content
449
+ ### Example D: Iterate on generated content (follow-up, no style step)
290
450
 
291
451
  ```
292
452
  User: "2番目のツイートをもっとユーモラスにして、絵文字も追加して"
293
453
  ```
294
454
 
295
- **Step 1:** Already have `thread_short_id` and `live_doc_id` from the previous call (e.g., Example B or C). No new LiveDoc lookup needed.
296
-
297
- **Step 2:** This is a follow-up — pass `--thread-id` with the saved `thread_short_id`.
455
+ 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
456
 
299
- **Step 3:** Follow-up call (`thread_short_id` already exists in session):
300
457
  ```bash
301
458
  node felo-superAgent/scripts/run_superagent.mjs \
302
459
  --query "/twitter-writer 上記で生成した2番目のツイートを修正してください。トーンをよりユーモラスで軽快にし、適切な絵文字を追加してください。内容の意図は変えないでください。" \
303
460
  --thread-id "THREAD_SHORT_ID" \
304
461
  --live-doc-id "LIVE_DOC_ID" \
305
- --accept-language ja
462
+ --accept-language ja \
463
+ --json
464
+ ```
465
+
466
+ **Save** updated `thread_short_id` and `live_doc_short_id` from JSON response fields `data.thread_short_id` and `data.live_doc_short_id`.
467
+
468
+ ---
469
+
470
+ ### Example E: User specifies style by name
471
+
472
+ ```
473
+ User: "Write a tweet about AI trends using my 'Bold Voice' style"
474
+ ```
475
+
476
+ **Step 1.5a:** User already named the style → extract full Style DNA via `--json`. No need to ask the user again.
477
+
478
+ **Step 3:** New conversation with that style:
479
+ ```bash
480
+ node felo-superAgent/scripts/run_superagent.mjs \
481
+ --query "/twitter-writer Write a tweet about AI trends" \
482
+ --live-doc-id "LIVE_DOC_ID" \
483
+ --skill-id twitter-writer \
484
+ --ext "$(node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
485
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
486
+ const j=JSON.parse(d);
487
+ const s=(j.list||[]).find(s=>s.name==='My Bold Voice');
488
+ const labels=(s.content?.labels?.en||[]).join(', ');
489
+ const block='Style name: '+s.name+'\nStyle labels: '+labels+'\nStyle DNA: '+s.content.styleDna;
490
+ console.log(JSON.stringify({brand_style_requirement:block}));
491
+ ")" \
492
+ --accept-language en \
493
+ --json
494
+ ```
495
+
496
+ ---
497
+
498
+ ### Example F: Style library is empty
499
+
500
+ ```
501
+ User: "Write a tweet about the new product launch"
502
+ ```
503
+
504
+ **Step 1.5b:** Fetch styles (names only):
505
+ ```bash
506
+ node felo-superAgent/scripts/run_style_library.mjs --category TWITTER --accept-language en --json | node -e "
507
+ const d=require('fs').readFileSync('/dev/stdin','utf8');
508
+ const j=JSON.parse(d);
509
+ if(!(j.list||[]).length)console.log('(No styles found)');
510
+ else console.log((j.list||[]).map(s=>s.name).join('\n'));
511
+ "
512
+ ```
513
+
514
+ Output: `(No styles found)`
515
+
516
+ **Skip silently.** Proceed directly without `--ext`:
517
+ ```bash
518
+ node felo-superAgent/scripts/run_superagent.mjs \
519
+ --query "/twitter-writer Write a tweet about the new product launch. Provide 3 versions with different tones." \
520
+ --live-doc-id "LIVE_DOC_ID" \
521
+ --skill-id twitter-writer \
522
+ --accept-language en \
523
+ --json
306
524
  ```
307
525
 
308
- **Step 4:** Save updated `thread_short_id` and `live_doc_short_id` from stderr `[state]`.
526
+ ---
527
+
528
+ ### Example G: User chooses no preference
529
+
530
+ ```
531
+ User: "帮我写一条关于新产品发布的推文"
532
+ ```
533
+
534
+ **Step 1.5:** Fetch styles, present list. User replies: `0` (no preference).
535
+
536
+ Proceed without `--ext`:
537
+ ```bash
538
+ node felo-superAgent/scripts/run_superagent.mjs \
539
+ --query "/twitter-writer 帮我写3条关于新产品发布的推文,每条风格略有不同。" \
540
+ --live-doc-id "LIVE_DOC_ID" \
541
+ --skill-id twitter-writer \
542
+ --accept-language zh \
543
+ --json
544
+ ```
309
545
 
310
546
  ---
311
547
 
@@ -316,13 +552,14 @@ node felo-superAgent/scripts/run_superagent.mjs \
316
552
  | Account not found or no tweets returned | Inform user, suggest trying a different username or providing tweet samples manually |
317
553
  | `FELO_API_KEY` not set | Stop and show setup instructions (same as `felo-superAgent` SKILL.md) |
318
554
  | SuperAgent call fails | Check `live_doc_id` validity; retry once with the same parameters |
319
- | User asks for Mode 2 with no style DNA and no account | Ask: "Would you like to provide a reference Twitter account to base the style on, or should I write in a general engaging style?" |
555
+ | Style library fetch fails | Log warning to stderr, skip silently, proceed without `--ext` |
556
+ | User asks for Mode 2 with no style DNA and no account | Proceed to Step 1.5 (style library selection) |
320
557
  | User explicitly requests a new canvas | Create a new LiveDoc: `node felo-livedoc/scripts/run_livedoc.mjs create --name "Twitter Writer" --json` |
321
558
  | Tweet content too long for query (>2000 chars) | Trim to the 10–15 most representative tweets; prioritize high-engagement ones |
322
559
 
323
560
  ## References
324
561
 
325
- - [felo-superAgent SKILL.md](../felo-superAgent/SKILL.md) — SuperAgent calling conventions and constraints
562
+ - [felo-superAgent SKILL.md](../felo-superAgent/SKILL.md) — SuperAgent calling conventions, `--ext` format, and style library script
326
563
  - [felo-x-search SKILL.md](../felo-x-search/SKILL.md) — X/Twitter search commands
327
564
  - [felo-livedoc SKILL.md](../felo-livedoc/SKILL.md) — LiveDoc management commands
328
565
  - [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.47",
3
+ "version": "0.2.49",
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";
@@ -1049,4 +1049,22 @@ program
1049
1049
  flushStdioThenExit(1);
1050
1050
  });
1051
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
+
1052
1070
  program.parse();
package/src/superAgent.js CHANGED
@@ -789,3 +789,136 @@ export async function superAgent(query, options = {}) {
789
789
  return 1;
790
790
  }
791
791
  }
792
+
793
+ /**
794
+ * Pick the best matching value from a multilingual map object.
795
+ * Falls back: exact match → base language (e.g. "zh" for "zh-Hans") → "en" → first available.
796
+ * Returns an array (may be empty).
797
+ * @param {Object} map - Multilingual map, e.g. { en: [...], "zh-Hans": [...] }
798
+ * @param {string} lang - Preferred language code.
799
+ */
800
+ function pickLangValue(map, lang) {
801
+ if (!map || typeof map !== 'object') return [];
802
+ if (map[lang]) return map[lang];
803
+ const base = lang.split('-')[0];
804
+ if (base !== lang && map[base]) return map[base];
805
+ if (map['en']) return map['en'];
806
+ const first = Object.values(map)[0];
807
+ return Array.isArray(first) ? first : [];
808
+ }
809
+
810
+ /**
811
+ * Format a single style entry into the brand_style_requirement string.
812
+ * Style name: <name>
813
+ * Style labels: <label1, label2> (from content.labels or content.tags, language-aware)
814
+ * Style DNA: <styleDna> (from content.styleDna, TWITTER type)
815
+ * Cover file ID: <coverFileId> (omitted if null/empty)
816
+ * @param {Object} s - Style entry from API.
817
+ * @param {string} lang - Language code for labels/tags.
818
+ */
819
+ function formatStyleEntry(s, lang) {
820
+ const lines = [];
821
+
822
+ lines.push(`Style name: ${s.name ?? ''}`);
823
+
824
+ const content = s.content ?? {};
825
+
826
+ // Labels — multilingual map (content.labels for TWITTER, content.tags for others)
827
+ const labelsMap = content.labels ?? content.tags ?? null;
828
+ if (labelsMap) {
829
+ const labelArr = pickLangValue(labelsMap, lang);
830
+ if (labelArr.length > 0) {
831
+ lines.push(`Style labels: ${labelArr.join(', ')}`);
832
+ }
833
+ }
834
+
835
+ // Style DNA (TWITTER type)
836
+ if (typeof content.styleDna === 'string' && content.styleDna.trim()) {
837
+ lines.push(`Style DNA: ${content.styleDna.trim()}`);
838
+ }
839
+
840
+ // Cover file ID — omit if null/empty
841
+ const coverId = s.coverFileId ?? s.cover_file_id ?? null;
842
+ if (coverId) {
843
+ lines.push(`Cover file ID: ${coverId}`);
844
+ }
845
+
846
+ return lines.join('\n');
847
+ }
848
+
849
+ /**
850
+ * List style library entries for a given category.
851
+ * @param {string} category - Style category (e.g. TWITTER, INSTAGRAM, LEMON8, NOTECOM, WEBSITE, IMAGE).
852
+ * @param {Object} [options]
853
+ * @param {boolean} [options.json] - Output raw JSON.
854
+ * @param {string} [options.acceptLanguage] - Language code for labels/tags (e.g. en, zh-Hans, ja). Default: en.
855
+ * @param {number} [options.timeoutMs] - Request timeout in ms.
856
+ * @returns {Promise<number>} Exit code 0 or 1.
857
+ */
858
+ export async function listStyleLibrary(category, options = {}) {
859
+ const apiKey = await getApiKey();
860
+ if (!apiKey) {
861
+ process.stderr.write(NO_KEY_MESSAGE.trim() + '\n');
862
+ return 1;
863
+ }
864
+
865
+ const apiBase = await getApiBase();
866
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
867
+ const lang = options.acceptLanguage || 'en';
868
+
869
+ const params = new URLSearchParams();
870
+ params.set('category', category);
871
+
872
+ const url = `${apiBase}/v2/brand/style-library/list?${params.toString()}`;
873
+
874
+ const controller = new AbortController();
875
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
876
+
877
+ try {
878
+ const res = await fetch(url, {
879
+ method: 'GET',
880
+ headers: {
881
+ Accept: 'application/json',
882
+ Authorization: `Bearer ${apiKey}`,
883
+ },
884
+ signal: controller.signal,
885
+ });
886
+
887
+ let data = {};
888
+ try {
889
+ data = await res.json();
890
+ } catch {
891
+ data = {};
892
+ }
893
+
894
+ if (!res.ok) {
895
+ throw new Error(`HTTP ${res.status}: ${getMessage(data)}`);
896
+ }
897
+ if (isApiError(data)) {
898
+ throw new Error(getMessage(data));
899
+ }
900
+
901
+ const list = data?.data?.list ?? [];
902
+
903
+ if (options.json) {
904
+ console.log(JSON.stringify(data?.data ?? {}, null, 2));
905
+ } else {
906
+ if (list.length === 0) {
907
+ console.log('(No styles found)');
908
+ } else {
909
+ const userStyles = list.filter((s) => !s.recommended);
910
+ const recommendedStyles = list.filter((s) => s.recommended);
911
+ const allFormatted = [...userStyles, ...recommendedStyles].map((s) => formatStyleEntry(s, lang));
912
+ console.log(allFormatted.join('\n\n'));
913
+ }
914
+ }
915
+
916
+ return 0;
917
+ } catch (err) {
918
+ const msg = err?.message || err;
919
+ process.stderr.write(`Error: ${msg}\n`);
920
+ return 1;
921
+ } finally {
922
+ clearTimeout(timer);
923
+ }
924
+ }