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.
- package/README.md +163 -51
- package/felo-superAgent/README.md +223 -280
- package/felo-superAgent/SKILL.md +291 -56
- package/felo-superAgent/scripts/run_style_library.mjs +213 -0
- package/felo-twitter-writer/README.md +117 -13
- package/felo-twitter-writer/SKILL.md +281 -44
- package/package.json +1 -1
- package/src/cli.js +19 -1
- package/src/superAgent.js +133 -0
|
@@ -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. **
|
|
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
|
-
4. **`--live-doc-id` is REQUIRED** for every SuperAgent call. Follow
|
|
19
|
-
- Reuse any `live_doc_id` already available in this session
|
|
20
|
-
- If none: run `node felo-livedoc/scripts/run_livedoc.mjs list --json`,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
--
|
|
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
|
|
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 (
|
|
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
|
-
│ └──
|
|
210
|
-
│
|
|
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 (
|
|
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
|
|
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
|
|
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:**
|
|
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
|
-
|
|
417
|
+
[Recommended styles]
|
|
418
|
+
2. darioamodei
|
|
277
419
|
|
|
278
|
-
|
|
420
|
+
0. No preference — use 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
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
|
|
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.
|
|
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
|
+
}
|