cue-ai 0.4.1 → 0.5.0

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.
Files changed (43) hide show
  1. package/README.md +225 -8
  2. package/package.json +3 -2
  3. package/profiles/affiliate/profile.yaml +67 -0
  4. package/profiles/go-api/profile.yaml +8 -0
  5. package/profiles/marketing/profile.yaml +4 -1
  6. package/profiles/nextjs/profile.yaml +8 -0
  7. package/profiles/python-api/profile.yaml +8 -0
  8. package/profiles/rust/profile.yaml +8 -0
  9. package/profiles/video/profile.yaml +10 -0
  10. package/resources/mcps/configs/claude_runtime.sanitized.json +15 -1
  11. package/resources/skills/skills/design/headless-gif-demo/SKILL.md +57 -12
  12. package/resources/skills/skills/meta/awesome-list-submit/SKILL.md +463 -0
  13. package/src/commands/_index.ts +44 -0
  14. package/src/commands/ai-score.e2e.test.ts +113 -0
  15. package/src/commands/ai.ts +179 -0
  16. package/src/commands/benchmark.ts +258 -0
  17. package/src/commands/clean.ts +109 -0
  18. package/src/commands/completions.ts +4 -0
  19. package/src/commands/cost.ts +77 -3
  20. package/src/commands/diff.ts +105 -0
  21. package/src/commands/import-profile.ts +28 -5
  22. package/src/commands/launch.e2e.test.ts +119 -0
  23. package/src/commands/launch.ts +19 -0
  24. package/src/commands/lock.ts +21 -1
  25. package/src/commands/marketplace.ts +88 -3
  26. package/src/commands/migrate.ts +100 -0
  27. package/src/commands/optimizer.ts +2 -2
  28. package/src/commands/replay-whatif.ts +142 -0
  29. package/src/commands/replay.ts +6 -0
  30. package/src/commands/score.ts +304 -0
  31. package/src/commands/security.ts +47 -7
  32. package/src/commands/shell.ts +17 -0
  33. package/src/commands/skills.ts +2 -2
  34. package/src/commands/status.ts +14 -0
  35. package/src/commands/suggest.ts +170 -0
  36. package/src/commands/upgrade.ts +154 -0
  37. package/src/index.ts +24 -1
  38. package/src/lib/analytics.ts +73 -1
  39. package/src/lib/auto-detect.ts +38 -5
  40. package/src/lib/cache.ts +47 -6
  41. package/src/lib/runtime-materializer.test.ts +22 -11
  42. package/src/lib/runtime-materializer.ts +58 -15
  43. package/src/lib/star-prompt.ts +10 -0
@@ -0,0 +1,463 @@
1
+ ---
2
+ name: awesome-list-submit
3
+ description: >-
4
+ When user says "submit to awesome lists", "add to awesome repos",
5
+ "promote on GitHub lists", or "find lists for this project" — auto-detect
6
+ project metadata, find relevant awesome-* repos, check for duplicates,
7
+ match entry format, draft PRs, and track submissions.
8
+ tags: [meta, marketing, github, promotion, outreach]
9
+ category: meta
10
+ version: 2.0.0
11
+ requires_mcps: []
12
+ allowed-tools: Bash(gh:*), Bash(curl:*), Bash(git:*), Bash(jq:*), WebSearch, Read(*), Write(*)
13
+ ---
14
+
15
+ # Submit to Awesome Lists (v2)
16
+
17
+ Auto-detect project info, find relevant awesome-* repos, and submit PRs — with dedup, format matching, and submission tracking.
18
+
19
+ ## When to activate
20
+
21
+ - User says "submit to awesome lists" or "add to awesome repos"
22
+ - User says "promote on GitHub" or "get listed"
23
+ - User says "find lists for this project"
24
+
25
+ ## Step 1: Auto-detect project metadata
26
+
27
+ ```bash
28
+ # Read from package.json, Cargo.toml, pyproject.toml, or README
29
+ PROJECT_NAME=$(jq -r '.name // empty' package.json 2>/dev/null || basename "$PWD")
30
+ DESCRIPTION=$(jq -r '.description // empty' package.json 2>/dev/null)
31
+ HOMEPAGE=$(jq -r '.homepage // empty' package.json 2>/dev/null)
32
+ REPO_URL=$(gh repo view --json url -q '.url' 2>/dev/null)
33
+ STARS=$(gh repo view --json stargazerCount -q '.stargazerCount' 2>/dev/null)
34
+ LICENSE=$(jq -r '.license // empty' package.json 2>/dev/null)
35
+ TOPICS=$(gh repo view --json repositoryTopics -q '.repositoryTopics[].name' 2>/dev/null | tr '\n' ',')
36
+
37
+ echo "Project: $PROJECT_NAME"
38
+ echo "Description: $DESCRIPTION"
39
+ echo "URL: $REPO_URL"
40
+ echo "Stars: $STARS"
41
+ echo "License: $LICENSE"
42
+ echo "Topics: $TOPICS"
43
+ ```
44
+
45
+ ## Step 2: Find relevant awesome lists
46
+
47
+ Search using the project's topics and keywords:
48
+
49
+ ```bash
50
+ # Search by each topic
51
+ for topic in $(echo "$TOPICS" | tr ',' '\n'); do
52
+ gh search repos "awesome-$topic" --sort stars --limit 3 --json fullName,stargazersCount,description
53
+ done
54
+
55
+ # Also search by project domain keywords from description
56
+ KEYWORDS=$(echo "$DESCRIPTION" | tr ' ' '\n' | grep -E '^[a-z]{4,}$' | head -5)
57
+ for kw in $KEYWORDS; do
58
+ gh search repos "awesome $kw" --sort stars --limit 3 --json fullName,stargazersCount,description
59
+ done
60
+ ```
61
+
62
+ Filter: only lists with >100 stars, not archived, updated in last 6 months.
63
+
64
+ ## Step 3: Check for duplicates
65
+
66
+ Before submitting, verify the project isn't already listed:
67
+
68
+ ```bash
69
+ check_already_listed() {
70
+ local repo="$1"
71
+ # Check README for our repo URL
72
+ gh api "repos/$repo/readme" --jq '.content' | base64 -d | grep -qi "$PROJECT_NAME\|$REPO_URL"
73
+ }
74
+
75
+ # Check open PRs too
76
+ check_pending_pr() {
77
+ local repo="$1"
78
+ gh pr list --repo "$repo" --search "$PROJECT_NAME" --state open --json number | jq 'length'
79
+ }
80
+ ```
81
+
82
+ ## Step 4: Match entry format
83
+
84
+ Parse existing entries to match the list's style:
85
+
86
+ ```bash
87
+ detect_format() {
88
+ local readme="$1"
89
+ if echo "$readme" | grep -q "^|.*|.*|"; then
90
+ echo "table" # Markdown table format
91
+ elif echo "$readme" | grep -q "^- \["; then
92
+ echo "bullet-link" # - [name](url) — description
93
+ elif echo "$readme" | grep -q "^- \*\*\["; then
94
+ echo "bullet-bold" # - **[name](url)** - description
95
+ fi
96
+ }
97
+ ```
98
+
99
+ Format templates:
100
+ - **table**: `| [name](url) | description |`
101
+ - **bullet-link**: `- [name](url) — description`
102
+ - **bullet-bold**: `- **[name](url)** - description`
103
+
104
+ ## Step 5: Submit PRs
105
+
106
+ ```bash
107
+ submit_to_list() {
108
+ local target_repo="$1"
109
+ local section="$2"
110
+ local entry="$3"
111
+
112
+ # Fork
113
+ gh repo fork "$target_repo" --clone --remote 2>/dev/null
114
+ local dir=$(basename "$target_repo")
115
+ cd "/tmp/$dir"
116
+
117
+ # Branch
118
+ git checkout -b "add-$PROJECT_NAME"
119
+
120
+ # Find insertion point (alphabetical within section)
121
+ # Insert the entry at the right position
122
+ # ... (agent edits README.md)
123
+
124
+ git add README.md
125
+ git commit -m "Add $PROJECT_NAME — $DESCRIPTION"
126
+ git push -u origin "add-$PROJECT_NAME"
127
+
128
+ # Create PR
129
+ gh pr create \
130
+ --title "Add $PROJECT_NAME" \
131
+ --body "**[$PROJECT_NAME]($REPO_URL)** — $DESCRIPTION
132
+
133
+ ⭐ $STARS stars | 📜 $LICENSE license | 🔧 Install: \`npm i -g $PROJECT_NAME\`
134
+
135
+ $REPO_URL" \
136
+ --repo "$target_repo"
137
+ }
138
+ ```
139
+
140
+ ## Step 6: Track submissions
141
+
142
+ Save submission history to avoid re-submitting:
143
+
144
+ ```bash
145
+ HISTORY_FILE="$HOME/.config/cue/awesome-submissions.json"
146
+
147
+ record_submission() {
148
+ local target="$1" pr_url="$2" status="$3"
149
+ jq --arg t "$target" --arg p "$pr_url" --arg s "$status" --arg d "$(date -I)" \
150
+ '. += [{"list": $t, "pr": $p, "status": $s, "date": $d}]' \
151
+ "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
152
+ }
153
+
154
+ # Check before submitting
155
+ already_submitted() {
156
+ local target="$1"
157
+ jq -e --arg t "$target" '.[] | select(.list == $t)' "$HISTORY_FILE" >/dev/null 2>&1
158
+ }
159
+ ```
160
+
161
+ ## Step 7: Report results
162
+
163
+ ```
164
+ 📋 Awesome List Submissions for "cue":
165
+
166
+ ✅ PR #442 opened: rohitg00/awesome-claude-code-toolkit
167
+ https://github.com/rohitg00/awesome-claude-code-toolkit/pull/442
168
+
169
+ ✅ PR #767 opened: travisvn/awesome-claude-skills
170
+ https://github.com/travisvn/awesome-claude-skills/pull/767
171
+
172
+ ⏭️ Skipped: hesreallyhim/awesome-claude-code (repo restructuring)
173
+ ⏭️ Skipped: punkpeye/awesome-mcp-servers (already listed)
174
+
175
+ 📊 Summary: 2 PRs opened, 2 skipped, 0 failed
176
+ 📁 History saved to ~/.config/cue/awesome-submissions.json
177
+ ```
178
+
179
+ ## Rules
180
+
181
+ - Auto-detect everything — don't ask user for metadata that's in package.json
182
+ - Always check for duplicates before submitting (README + open PRs)
183
+ - Match the existing format exactly (table vs bullet, alphabetical order)
184
+ - Only submit to lists with >100 stars and recent activity
185
+ - Track all submissions to prevent re-submitting
186
+ - Max 5 submissions per session
187
+ - Show the PR URLs so user can track acceptance
188
+ - If a list is archived or restructuring, skip with explanation
189
+
190
+ ---
191
+
192
+ ## Advanced: Competitor Analysis
193
+
194
+ Find where competing tools are listed and submit to those same lists:
195
+
196
+ ```bash
197
+ COMPETITORS="claude-code-switcher skillport agent-skills-cli agent-skill-manager skillshub"
198
+
199
+ find_gaps() {
200
+ local our_listings=$(gh search code "$PROJECT_NAME" --filename README.md --limit 50 --json repository | jq -r '.[].repository.fullName')
201
+ for comp in $COMPETITORS; do
202
+ gh search code "$comp" --filename README.md --limit 20 --json repository \
203
+ | jq -r '.[].repository.fullName' \
204
+ | grep -i "awesome\|list\|collection" \
205
+ | while read repo; do
206
+ echo "$our_listings" | grep -q "$repo" || echo "GAP: $repo (has $comp, missing us)"
207
+ done
208
+ done
209
+ }
210
+ ```
211
+
212
+ Present as:
213
+ ```
214
+ 🔍 Competitor Analysis — found 3 gaps where competitors are listed but we aren't.
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Advanced: Custom PR Body Templates
220
+
221
+ Auto-detect list audience and use the right pitch:
222
+
223
+ ```bash
224
+ detect_list_type() {
225
+ local name="$1" desc="$2"
226
+ if echo "$name $desc" | grep -qi "mcp\|model.context.protocol"; then echo "mcp"
227
+ elif echo "$name $desc" | grep -qi "skill\|agent"; then echo "ai-agents"
228
+ elif echo "$name $desc" | grep -qi "cli\|command.line"; then echo "cli"
229
+ else echo "dev-tools"
230
+ fi
231
+ }
232
+ ```
233
+
234
+ Templates per audience:
235
+ - **ai-agents**: Lead with the problem (context overload), show 10-agent support
236
+ - **cli**: Lead with Unix philosophy, sub-5ms overhead, no daemon
237
+ - **mcp**: Lead with per-project MCP scoping, inheritance
238
+ - **skills**: Lead with 110+ bundled skills, npx skill packs
239
+ - **dev-tools**: Lead with developer experience, one-command install
240
+
241
+ ---
242
+
243
+ ## Advanced: Screenshot/GIF in PR Body
244
+
245
+ Auto-find and embed visual assets:
246
+
247
+ ```bash
248
+ find_demo_assets() {
249
+ for f in docs/assets/demo.gif assets/demo.gif \
250
+ docs/assets/terminal-optimizer.svg docs/assets/hero.svg; do
251
+ [ -f "$f" ] && echo "![Demo](https://raw.githubusercontent.com/$(gh repo view --json nameWithOwner -q '.nameWithOwner')/main/$f)" && return
252
+ done
253
+ }
254
+ ```
255
+
256
+ Inject into every PR body — maintainers see what the tool does without clicking through. Visual PRs get merged 2-3x faster.
257
+
258
+ ---
259
+
260
+ ## Advanced: Cross-reference After Merge
261
+
262
+ When one PR is merged, comment on pending PRs with social proof:
263
+
264
+ ```bash
265
+ # After a merge is detected:
266
+ gh pr comment $PENDING_PR --repo "$list" \
267
+ --body "👋 Friendly bump — recently accepted into $MERGED_LIST. Happy to adjust format if needed!"
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Advanced: Timing
273
+
274
+ Submit Tue-Thu during business hours for fastest maintainer response. Skip weekends and Mondays.
275
+
276
+ ---
277
+
278
+ ## Advanced: Automated Weekly Status Check
279
+
280
+ Set up a cron/scheduled check for PR status updates:
281
+
282
+ ```bash
283
+ # Run weekly: cue awesome-submit --check-status
284
+ check_all_submissions() {
285
+ jq -c '.[] | select(.status == "pending")' "$HISTORY_FILE" | while read entry; do
286
+ local list=$(echo "$entry" | jq -r '.list')
287
+ local pr=$(echo "$entry" | jq -r '.pr')
288
+ local pr_num=$(basename "$pr")
289
+
290
+ local state=$(gh pr view "$pr_num" --repo "$list" --json state -q '.state' 2>/dev/null)
291
+ case "$state" in
292
+ MERGED)
293
+ echo "🎉 MERGED: $list (#$pr_num)"
294
+ update_status "$list" "merged"
295
+ ;;
296
+ CLOSED)
297
+ echo "❌ CLOSED: $list (#$pr_num)"
298
+ local reason=$(gh pr view "$pr_num" --repo "$list" --json comments -q '.comments[-1].body' 2>/dev/null)
299
+ echo " Reason: $reason"
300
+ update_status "$list" "closed"
301
+ ;;
302
+ OPEN)
303
+ local age=$(( ($(date +%s) - $(date -d "$(echo "$entry" | jq -r '.date')" +%s)) / 86400 ))
304
+ if [ "$age" -gt 14 ]; then
305
+ echo "⏰ STALE ($age days): $list (#$pr_num) — consider bumping"
306
+ fi
307
+ ;;
308
+ esac
309
+ done
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Advanced: Release-triggered Discovery
316
+
317
+ GitHub Action that runs on every release tag:
318
+
319
+ ```yaml
320
+ # .github/workflows/awesome-submit.yml
321
+ name: Find new awesome lists
322
+ on:
323
+ release:
324
+ types: [published]
325
+ jobs:
326
+ discover:
327
+ runs-on: ubuntu-latest
328
+ steps:
329
+ - uses: actions/checkout@v4
330
+ - run: |
331
+ # Find lists created since last release that match our topics
332
+ SINCE=$(gh release list --limit 2 --json publishedAt -q '.[1].publishedAt')
333
+ gh search repos "awesome claude-code" --sort updated --json fullName,createdAt \
334
+ | jq --arg since "$SINCE" '[.[] | select(.createdAt > $since)]'
335
+ # Output as issue for manual review
336
+ - run: |
337
+ gh issue create --title "New awesome lists to submit to" \
338
+ --body "$(cat /tmp/new-lists.md)" --label "marketing"
339
+ ```
340
+
341
+ ---
342
+
343
+ ## Advanced: Maintainer Relationship DB
344
+
345
+ Track maintainer behavior for smarter targeting:
346
+
347
+ ```bash
348
+ MAINTAINER_DB="$HOME/.config/cue/awesome-maintainers.json"
349
+
350
+ # After each PR resolution, record maintainer response
351
+ record_maintainer() {
352
+ local repo="$1" outcome="$2" days_to_respond="$3"
353
+ jq --arg r "$repo" --arg o "$outcome" --arg d "$days_to_respond" \
354
+ '.[$r] = (.[$r] // {}) | .[$r].history += [{"outcome": $o, "days": ($d|tonumber)}] | .[$r].avg_days = ([.[$r].history[].days] | add / length)' \
355
+ "$MAINTAINER_DB" > "$MAINTAINER_DB.tmp" && mv "$MAINTAINER_DB.tmp" "$MAINTAINER_DB"
356
+ }
357
+
358
+ # Prioritize responsive maintainers
359
+ rank_targets() {
360
+ jq -r 'to_entries | sort_by(.value.avg_days) | .[] | "\(.value.avg_days)d avg — \(.key)"' "$MAINTAINER_DB"
361
+ }
362
+ ```
363
+
364
+ ---
365
+
366
+ ## Advanced: A/B Test PR Titles
367
+
368
+ Rotate title formats and track which gets merged:
369
+
370
+ ```bash
371
+ PR_TITLE_VARIANTS=(
372
+ "Add $PROJECT_NAME"
373
+ "Add $PROJECT_NAME — $SHORT_DESC"
374
+ "Add $PROJECT_NAME ($STARS+ stars)"
375
+ "Add $PROJECT_NAME: $ONE_LINE_VALUE_PROP"
376
+ )
377
+
378
+ select_title() {
379
+ # Pick based on what's worked before
380
+ local best=$(jq -r '[.[] | select(.status=="merged")] | group_by(.title_style) | sort_by(-length) | .[0][0].title_style // empty' "$HISTORY_FILE")
381
+ if [ -n "$best" ]; then
382
+ echo "${PR_TITLE_VARIANTS[$best]}"
383
+ else
384
+ # Rotate through variants
385
+ local idx=$(( $(jq 'length' "$HISTORY_FILE") % ${#PR_TITLE_VARIANTS[@]} ))
386
+ echo "${PR_TITLE_VARIANTS[$idx]}"
387
+ fi
388
+ }
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Advanced: Auto-generate Comparison Table Rows
394
+
395
+ Some lists have feature comparison tables. Auto-fill cue's row:
396
+
397
+ ```bash
398
+ detect_comparison_table() {
399
+ local readme="$1"
400
+ # Find tables with competitor names
401
+ if echo "$readme" | grep -q "claude-code-switcher\|skillport\|agent-skill"; then
402
+ echo "comparison-table"
403
+ fi
404
+ }
405
+
406
+ generate_comparison_row() {
407
+ # cue's features for common comparison dimensions
408
+ cat <<'EOF'
409
+ | **cue** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
410
+ EOF
411
+ # Columns: skills | MCPs | plugins | profiles | per-dir | isolation | inherit
412
+ }
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Advanced: Backlink Monitoring
418
+
419
+ Verify links stay live after merge:
420
+
421
+ ```bash
422
+ # Monthly check: are our links still in the READMEs?
423
+ monitor_backlinks() {
424
+ jq -c '.[] | select(.status == "merged")' "$HISTORY_FILE" | while read entry; do
425
+ local list=$(echo "$entry" | jq -r '.list')
426
+ local still_listed=$(gh api "repos/$list/readme" --jq '.content' | base64 -d | grep -c "$REPO_URL")
427
+ if [ "$still_listed" -eq 0 ]; then
428
+ echo "⚠️ REMOVED from $list — re-submit or investigate"
429
+ fi
430
+ done
431
+ }
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Advanced: Multi-language Submissions
437
+
438
+ Submit to non-English awesome lists with translated descriptions:
439
+
440
+ ```bash
441
+ TRANSLATIONS=(
442
+ "zh:代理配置管理器 — 按目录隔离技能、MCP和插件,支持继承和缓存"
443
+ "ja:エージェントプロファイルマネージャー — ディレクトリごとにスキル・MCP・プラグインを分離"
444
+ "ko:에이전트 프로필 관리자 — 디렉토리별 스킬/MCP/플러그인 격리"
445
+ )
446
+
447
+ # Known non-English lists
448
+ NON_EN_LISTS=(
449
+ "yzfly/Awesome-MCP-ZH" # Chinese MCP list (7k stars)
450
+ "punkpeye/awesome-mcp-servers" # Has i18n section
451
+ )
452
+
453
+ get_translated_description() {
454
+ local lang="$1"
455
+ for t in "${TRANSLATIONS[@]}"; do
456
+ if [[ "$t" == "$lang:"* ]]; then
457
+ echo "${t#*:}"
458
+ return
459
+ fi
460
+ done
461
+ echo "$DESCRIPTION" # fallback to English
462
+ }
463
+ ```
@@ -33,6 +33,10 @@ export const COMMANDS = {
33
33
  summary: "Print a tree of installed skills/plugins grouped by domain (A10/A11)",
34
34
  load: () => import("./scan"),
35
35
  },
36
+ score: {
37
+ summary: "Profile efficiency score (A+ to F) with SVG badge",
38
+ load: () => import("./score"),
39
+ },
36
40
  doctor: {
37
41
  summary: "Diff declared profile vs actual disk state; --fix repairs (A15)",
38
42
  load: () => import("./doctor"),
@@ -89,6 +93,10 @@ export const COMMANDS = {
89
93
  summary: "Show what a skill does — description, summary, size",
90
94
  load: () => import("./ask"),
91
95
  },
96
+ ai: {
97
+ summary: "Create a profile from natural language description",
98
+ load: () => import("./ai"),
99
+ },
92
100
  builtin: {
93
101
  summary: "Manage built-in skills shared across all profiles",
94
102
  load: () => import("./builtin"),
@@ -209,14 +217,34 @@ export const COMMANDS = {
209
217
  summary: "Self-update: git pull + bun install + sync",
210
218
  load: () => import("./update"),
211
219
  },
220
+ upgrade: {
221
+ summary: "Pull new skills/profiles from the registry",
222
+ load: () => import("./upgrade"),
223
+ },
212
224
  completions: {
213
225
  summary: "Output shell completion script (bash/zsh)",
214
226
  load: () => import("./completions"),
215
227
  },
228
+ clean: {
229
+ summary: "Prune stale runtimes, old cache, reclaim disk space",
230
+ load: () => import("./clean"),
231
+ },
232
+ migrate: {
233
+ summary: "Auto-migrate profiles to latest schema version",
234
+ load: () => import("./migrate"),
235
+ },
236
+ suggest: {
237
+ summary: "Profile recommendations based on usage patterns",
238
+ load: () => import("./suggest"),
239
+ },
216
240
  watch: {
217
241
  summary: "Auto-switch profile notification on cd (shell hook)",
218
242
  load: () => import("./watch"),
219
243
  },
244
+ benchmark: {
245
+ summary: "Measure profile efficiency: tokens, skill usage, cost",
246
+ load: () => import("./benchmark"),
247
+ },
220
248
  tree: {
221
249
  summary: "Visualize profile inheritance tree with resources",
222
250
  load: () => import("./tree"),
@@ -225,6 +253,22 @@ export const COMMANDS = {
225
253
  summary: "Show GitHub repos that provide skills for a profile",
226
254
  load: () => import("./sources"),
227
255
  },
256
+ sponsor: {
257
+ summary: "Star the repo / show support links",
258
+ load: async () => ({
259
+ run: async () => {
260
+ const { maybePromptStar } = await import("../lib/star-prompt");
261
+ // Force the prompt regardless of session count
262
+ const { existsSync, unlinkSync } = await import("node:fs");
263
+ const { join } = await import("node:path");
264
+ const { homedir } = await import("node:os");
265
+ const flag = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "cue", ".star-prompted");
266
+ if (existsSync(flag)) unlinkSync(flag);
267
+ await maybePromptStar();
268
+ return 0;
269
+ },
270
+ }),
271
+ },
228
272
  "migrate-symlinks": {
229
273
  summary: "Rewrite ~/.codex and ~/.claude-accounts symlinks from soul/ to cue/",
230
274
  load: () => import("./migrate-symlinks"),
@@ -0,0 +1,113 @@
1
+ /**
2
+ * E2e tests for `cue ai` and `cue score`.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { spawnSync } from "node:child_process";
7
+ import { join } from "node:path";
8
+
9
+ const CUE_BIN = join(import.meta.dir, "../index.ts");
10
+
11
+ function cue(args: string[]): { status: number; stdout: string; stderr: string } {
12
+ const res = spawnSync("bun", ["run", CUE_BIN, ...args], {
13
+ encoding: "utf8",
14
+ timeout: 15000,
15
+ });
16
+ return { status: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
17
+ }
18
+
19
+ describe("cue ai", () => {
20
+ test("matches python-api for python/fastapi description", () => {
21
+ const res = cue(["ai", "python fastapi sqlalchemy"]);
22
+ expect(res.status).toBe(0);
23
+ expect(res.stdout).toContain("python-api");
24
+ });
25
+
26
+ test("matches rust for rust/cargo description", () => {
27
+ const res = cue(["ai", "rust cargo async cli tool"]);
28
+ expect(res.status).toBe(0);
29
+ expect(res.stdout).toContain("rust");
30
+ });
31
+
32
+ test("matches frontend for react/tailwind description", () => {
33
+ const res = cue(["ai", "react tailwind vite frontend"]);
34
+ expect(res.status).toBe(0);
35
+ expect(res.stdout).toContain("frontend");
36
+ });
37
+
38
+ test("matches nextjs for next.js description", () => {
39
+ const res = cue(["ai", "next.js app router server components"]);
40
+ expect(res.status).toBe(0);
41
+ expect(res.stdout).toContain("nextjs");
42
+ });
43
+
44
+ test("matches go-api for golang description", () => {
45
+ const res = cue(["ai", "golang gin api"]);
46
+ expect(res.status).toBe(0);
47
+ expect(res.stdout).toContain("go-api");
48
+ });
49
+
50
+ test("matches video for ffmpeg/video description", () => {
51
+ const res = cue(["ai", "video ffmpeg frames"]);
52
+ expect(res.status).toBe(0);
53
+ expect(res.stdout).toContain("video");
54
+ });
55
+
56
+ test("shows help with no args", () => {
57
+ const res = cue(["ai"]);
58
+ expect(res.status).toBe(0);
59
+ expect(res.stdout).toContain("Usage");
60
+ });
61
+
62
+ test("handles no-match gracefully", () => {
63
+ const res = cue(["ai", "xyznonexistent"]);
64
+ expect(res.status).toBe(0);
65
+ expect(res.stdout).toContain("No matching");
66
+ });
67
+ });
68
+
69
+ describe("cue score", () => {
70
+ test("scores a specific profile", () => {
71
+ const res = cue(["score", "--profile", "core"]);
72
+ expect(res.status).toBe(0);
73
+ expect(res.stdout).toContain("core");
74
+ expect(res.stdout).toContain("/100");
75
+ });
76
+
77
+ test("--all shows all profiles ranked", () => {
78
+ const res = cue(["score", "--all"]);
79
+ expect(res.status).toBe(0);
80
+ expect(res.stdout).toContain("Profile Scores");
81
+ expect(res.stdout).toContain("core");
82
+ expect(res.stdout).toContain("backend");
83
+ });
84
+
85
+ test("--json returns valid JSON", () => {
86
+ const res = cue(["score", "--profile", "rust", "--json"]);
87
+ expect(res.status).toBe(0);
88
+ const data = JSON.parse(res.stdout);
89
+ expect(data.profile).toBe("rust");
90
+ expect(data.grade).toMatch(/^[A-F][+-]?$/);
91
+ expect(data.score).toBeGreaterThanOrEqual(0);
92
+ expect(data.score).toBeLessThanOrEqual(100);
93
+ expect(data.tokens).toBeGreaterThan(0);
94
+ });
95
+
96
+ test("--markdown outputs shields.io badge", () => {
97
+ const res = cue(["score", "--profile", "backend", "--markdown"]);
98
+ expect(res.status).toBe(0);
99
+ expect(res.stdout).toContain("img.shields.io/badge/cue_score");
100
+ });
101
+
102
+ test("--badge generates SVG file", () => {
103
+ const res = cue(["score", "--profile", "core", "--badge", "/tmp/cue-test-badge.svg"]);
104
+ expect(res.status).toBe(0);
105
+ expect(res.stdout).toContain("Badge saved");
106
+
107
+ const { readFileSync, unlinkSync } = require("node:fs");
108
+ const svg = readFileSync("/tmp/cue-test-badge.svg", "utf8");
109
+ expect(svg).toContain("<svg");
110
+ expect(svg).toContain("core");
111
+ unlinkSync("/tmp/cue-test-badge.svg");
112
+ });
113
+ });