felo-ai 0.2.47 → 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.
@@ -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. **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
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 stderr `[state]` line. Use them in subsequent calls.
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 stderr `[state]` line. Save for follow-up calls.
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 with general tweet writing (SuperAgent will use its default twitter-writer behavior)
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
- **New conversation (no `thread_short_id` in session):**
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
- --accept-language LANG
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 stderr `[state]` line.
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 (Creation)
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
- │ └── If no style DNA available: ask user if they want to provide
210
- a reference account, or proceed with general twitter-writer style
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 (first call in session — no `thread_short_id` yet):
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 stderr `[state]`.
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 Example A):
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:** No style DNA needed, proceed directly
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
- **Step 2:** Get `live_doc_id`
415
+ [Recommended styles]
416
+ 2. darioamodei
277
417
 
278
- **Step 3:** New conversation with `twitter-writer` (first call in session no `thread_short_id` yet):
418
+ 0. No preferenceuse 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
- **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`.
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
- **Step 4:** Save updated `thread_short_id` and `live_doc_short_id` from stderr `[state]`.
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
- | 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?" |
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 constraints
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.47",
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";
@@ -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
+ }