fleece-cli 0.3.1__tar.gz → 0.4.0__tar.gz

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 (62) hide show
  1. fleece_cli-0.4.0/.claude/skills/fleece-profile.md +81 -0
  2. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.github/workflows/publish.yml +3 -0
  3. fleece_cli-0.4.0/CLAUDE.md +123 -0
  4. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/PKG-INFO +1 -1
  5. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/cli.py +94 -8
  6. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/db.py +85 -0
  7. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/pyproject.toml +1 -1
  8. fleece_cli-0.3.1/CLAUDE.md +0 -49
  9. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.agents/skills/fleece/SKILL.md +0 -0
  10. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-card.md +0 -0
  11. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-compare.md +0 -0
  12. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-credits.md +0 -0
  13. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-flights.md +0 -0
  14. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-hotels.md +0 -0
  15. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-mcc.md +0 -0
  16. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-news.md +0 -0
  17. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-partners.md +0 -0
  18. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-rates.md +0 -0
  19. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-recommend.md +0 -0
  20. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-roi.md +0 -0
  21. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.claude/skills/fleece-wallet.md +0 -0
  22. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.devcontainer/devcontainer.json +0 -0
  23. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.github/workflows/publish-skills.yml +0 -0
  24. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.gitignore +0 -0
  25. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/.streamlit/config.toml +0 -0
  26. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/Advertiser Disclosure.md +0 -0
  27. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/KnowledgeCatalog.json +0 -0
  28. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/LICENSE +0 -0
  29. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/MyCards.json +0 -0
  30. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/README.md +0 -0
  31. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/app.yaml +0 -0
  32. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/assets/default_card.png +0 -0
  33. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/assets/default_card.svg +0 -0
  34. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/docs/CNAME +0 -0
  35. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/docs/SEO.md +0 -0
  36. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/docs/index.html +0 -0
  37. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/docs/robots.txt +0 -0
  38. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/docs/sitemap.xml +0 -0
  39. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/fleece poc.ipynb +0 -0
  40. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/fleece.py +0 -0
  41. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/image_service.py +0 -0
  42. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/install.sh +0 -0
  43. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/mcc_codes.jsonl +0 -0
  44. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/migrate.py +0 -0
  45. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/pages/credit_cards.py +0 -0
  46. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/pages/my_credit_cards.py +0 -0
  47. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/plan.md +0 -0
  48. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/pointsyeah.py +0 -0
  49. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/prompts/agent_system_prompt.py +0 -0
  50. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/reference/Analyzer.md +0 -0
  51. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/requirements-test.txt +0 -0
  52. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/requirements.txt +0 -0
  53. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/style.css +0 -0
  54. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/test_fleece.py +0 -0
  55. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tests/__init__.py +0 -0
  56. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tests/test_brave_client.py +0 -0
  57. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tests/test_cli.py +0 -0
  58. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tests/test_pointsyeah.py +0 -0
  59. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tests/test_tools.py +0 -0
  60. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tools/__init__.py +0 -0
  61. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tools/brave_client.py +0 -0
  62. {fleece_cli-0.3.1 → fleece_cli-0.4.0}/tools/credit_card_tools.py +0 -0
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: fleece-profile
3
+ description: Manage the user's spending profile — monthly spend by category, annual fee tolerance, points programs, home airport, and redemption goal. Profile context is automatically injected into fleece wallet, fleece roi, and fleece recommend. Use when setting up or updating the user's preferences.
4
+ ---
5
+
6
+ # /fleece-profile
7
+
8
+ Manages the user's persistent spending profile stored in `fleece.db`. Once set, profile context is automatically injected into `fleece wallet`, `fleece roi`, and `fleece recommend` — no need to re-enter spend amounts each time.
9
+
10
+ ## When to use
11
+ - User wants to set up their profile so research commands are personalised
12
+ - User updates their spending habits or preferences
13
+ - User asks why recommendations don't match their situation (profile may be empty or stale)
14
+ - Before running `fleece recommend` or `fleece roi` for the first time
15
+
16
+ ## Profile fields
17
+
18
+ | Field | Description |
19
+ |---|---|
20
+ | `dining_monthly` | Monthly dining spend ($) |
21
+ | `groceries_monthly` | Monthly groceries spend ($) |
22
+ | `travel_monthly` | Monthly travel spend ($) |
23
+ | `gas_monthly` | Monthly gas spend ($) |
24
+ | `other_monthly` | Monthly other spend ($) |
25
+ | `annual_fee_tolerance` | Max annual fee willing to pay ($) |
26
+ | `points_programs` | Preferred points programs (e.g. Amex MR, Chase UR) |
27
+ | `home_airport` | Home airport IATA code (e.g. JFK) |
28
+ | `goal` | Current redemption goal (e.g. business class to Tokyo) |
29
+ | `preferences` | Other preferences (e.g. no foreign transaction fees) |
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ # See all available fields
35
+ fleece profile fields
36
+
37
+ # Show current profile
38
+ fleece profile show
39
+ fleece profile show --json
40
+
41
+ # Set fields
42
+ fleece profile set dining_monthly 600
43
+ fleece profile set travel_monthly 300
44
+ fleece profile set annual_fee_tolerance 550
45
+ fleece profile set points_programs "Amex MR, Chase UR"
46
+ fleece profile set home_airport JFK
47
+ fleece profile set goal "business class to Tokyo, June 2027"
48
+
49
+ # Clear a field
50
+ fleece profile unset goal
51
+ ```
52
+
53
+ ## How profile enriches other commands
54
+
55
+ Once set, you don't need to pass spend amounts manually:
56
+
57
+ ```bash
58
+ # Without profile: must pass all spend flags
59
+ fleece roi "Amex Gold" --dining 600 --travel 300 --other 800
60
+
61
+ # With profile set: spend values pulled automatically
62
+ fleece roi "Amex Gold"
63
+
64
+ # fleece wallet and fleece recommend also use profile context automatically
65
+ fleece wallet
66
+ fleece recommend "travel rewards"
67
+ ```
68
+
69
+ ## Setup workflow
70
+
71
+ ```bash
72
+ fleece profile set dining_monthly 600
73
+ fleece profile set groceries_monthly 400
74
+ fleece profile set travel_monthly 300
75
+ fleece profile set gas_monthly 150
76
+ fleece profile set other_monthly 800
77
+ fleece profile set annual_fee_tolerance 250
78
+ fleece profile set home_airport JFK
79
+ fleece profile set goal "Fly business to Tokyo in 2027"
80
+ fleece profile show
81
+ ```
@@ -5,6 +5,9 @@ on:
5
5
  tags:
6
6
  - "v*"
7
7
 
8
+ env:
9
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
10
+
8
11
  jobs:
9
12
  build:
10
13
  runs-on: ubuntu-latest
@@ -0,0 +1,123 @@
1
+ # Fleece — Credit Card Research & Redemption
2
+
3
+ ## Project Overview
4
+
5
+ **Fleece** is a credit card research and award redemption toolkit. Tagline: "Find the best card for deal saviors."
6
+
7
+ It has two surfaces:
8
+ 1. **Streamlit chatbot** (`fleece.py`) — conversational AI assistant backed by OpenAI
9
+ 2. **CLI** (`cli.py`) — 12 commands for card research, wallet analysis, MCC lookup, and award redemption
10
+
11
+ Published on PyPI as [`fleece-cli`](https://pypi.org/project/fleece-cli/) · current version **0.3.1**
12
+
13
+ ---
14
+
15
+ ## Tech Stack
16
+
17
+ | Layer | Technology |
18
+ |---|---|
19
+ | Chatbot frontend | Streamlit |
20
+ | Chatbot LLM | OpenAI (gpt-3.5-turbo, gpt-4, gpt-4o) via LangChain |
21
+ | Chatbot memory | ConversationEntityMemory |
22
+ | CLI framework | Typer |
23
+ | Live research | Brave Search API |
24
+ | Card portfolio | SQLite (`fleece.db`) via `db.py` |
25
+ | MCC dataset | Bundled `mcc_codes.jsonl` (981 codes, offline) |
26
+ | Redemption URLs | `pointsyeah.py` (pure stdlib, no deps) |
27
+ | Language | Python 3.11+ |
28
+
29
+ ---
30
+
31
+ ## CLI Commands
32
+
33
+ ### Research (requires `BRAVE_API_KEY`)
34
+ | Command | Description |
35
+ |---|---|
36
+ | `fleece card "<name>"` | Full card report — fees, welcome offer, rates, credits, benefits |
37
+ | `fleece rates "<name>"` | Earning rates by spend category |
38
+ | `fleece partners "<name>"` | Transfer partners, ratios, timing |
39
+ | `fleece credits "<name>"` | Statement credits and perks |
40
+ | `fleece news "<name>"` | Recent changes (past month, freshness-filtered) |
41
+ | `fleece compare "<A>" "<B>"` | Side-by-side comparison |
42
+ | `fleece wallet` | Portfolio analysis — coverage, overlaps, gaps, next-card suggestions |
43
+ | `fleece roi "<name>"` | First-year ROI estimate by spend profile |
44
+ | `fleece recommend "<profile>"` | Card recommendations for a spending profile |
45
+
46
+ ### Redemption (no API key needed — work fully offline)
47
+ | Command | Description |
48
+ |---|---|
49
+ | `fleece mcc <code>` | Offline MCC code lookup (981 codes bundled). Add `--wallet` to cross-reference saved cards |
50
+ | `fleece flights <ORIGIN> <DEST> --date <YYYY-MM-DD>` | PointsYeah award flight search URL |
51
+ | `fleece hotels "<location>" --checkin <date> --checkout <date>` | PointsYeah award hotel search URL |
52
+
53
+ All commands support `--json` for agent-friendly output and `-` to read arguments from stdin.
54
+
55
+ ### BRAVE_API_KEY
56
+ Optional at startup — checked only when a research command actually runs. `mcc`, `flights`, and `hotels` work with no key set.
57
+
58
+ ---
59
+
60
+ ## Key Files
61
+
62
+ | File | Purpose |
63
+ |---|---|
64
+ | `cli.py` | Main CLI entry point (Typer app) |
65
+ | `fleece.py` | Streamlit chatbot app |
66
+ | `db.py` | SQLite helpers for card portfolio (`fleece.db`) |
67
+ | `pointsyeah.py` | PointsYeah URL generation (pure stdlib, merged from archived `pointsyeah-cli`) |
68
+ | `mcc_codes.jsonl` | Bundled MCC dataset (981 codes, source: greggles/mcc-codes) |
69
+ | `tools/brave_client.py` | Brave Search API client |
70
+ | `tools/credit_card_tools.py` | LangChain tools for the chatbot |
71
+ | `pyproject.toml` | Package config — hatchling build, `fleece` entry point |
72
+ | `install.sh` | Installs Claude Code skills and/or agent skill |
73
+
74
+ ---
75
+
76
+ ## Agent Skills
77
+
78
+ ### Claude Code (`/.claude/skills/`)
79
+ 12 slash commands installed via `bash install.sh --claude`:
80
+
81
+ **Research:** `/fleece-card` `/fleece-rates` `/fleece-partners` `/fleece-credits` `/fleece-news` `/fleece-compare` `/fleece-wallet` `/fleece-roi` `/fleece-recommend`
82
+
83
+ **Redemption:** `/fleece-mcc` `/fleece-flights` `/fleece-hotels`
84
+
85
+ ### ClawHub / OpenClaw (`/.agents/skills/fleece/SKILL.md`)
86
+ Published on ClawHub as `fleece@1.3.0`. Install via `clawhub install fleece` or `bash install.sh --agents`.
87
+
88
+ ---
89
+
90
+ ## CI/CD
91
+
92
+ | Workflow | Trigger | Action |
93
+ |---|---|---|
94
+ | `publish.yml` | Push `v*` tag | Build and publish to PyPI via OIDC trusted publishing |
95
+ | `publish-skills.yml` | Push to `main` touching `.agents/skills/` or `.claude/skills/` | Publish to ClawHub via `CLAWHUB_TOKEN` secret |
96
+
97
+ GitHub environment `pypi` is required for the PyPI workflow (OIDC).
98
+
99
+ ---
100
+
101
+ ## Landing Page
102
+
103
+ `docs/` is served as GitHub Pages at **https://getfleece.io/**.
104
+
105
+ Contains `index.html`, `sitemap.xml`, `robots.txt`. Submitted to Google Search Console. JSON-LD structured data included.
106
+
107
+ SEO notes tracked in `docs/SEO.md`.
108
+
109
+ ---
110
+
111
+ ## Infrastructure Notes
112
+
113
+ - **Databricks**: No resources available. Do not suggest Databricks solutions until provisioned.
114
+ - **pointsyeah-cli**: Archived. All functionality merged into fleece (`pointsyeah.py`, `fleece flights`, `fleece hotels`).
115
+
116
+ ---
117
+
118
+ ## Development Notes
119
+
120
+ - Author: Yuan Chen
121
+ - Created: March 16, 2025
122
+ - Chatbot uses custom CSS styling (`style.css`)
123
+ - OpenAI API key entered via Streamlit sidebar — not stored
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleece-cli
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Credit card research and redemption CLI — live data via Brave Search, PointsYeah URL generation, designed for humans and AI agents.
5
5
  Author-email: Yuan Chen <cysbc1999@gmail.com>
6
6
  License: MIT
@@ -250,14 +250,18 @@ def wallet(
250
250
  except Exception as e:
251
251
  _error_exit(str(e), code=1)
252
252
 
253
+ import db as _db
254
+ profile_ctx = _db.profile_as_context()
255
+
253
256
  combined = "\n\n".join(parts)
254
- combined += (
255
- "\n\nBased on the above, identify:\n"
256
- "1. Category coverage map\n"
257
- "2. Overlapping benefits or redundant categories\n"
258
- "3. Gaps categories with no bonus multiplier\n"
259
- "4. Top 1-2 cards that would complement this portfolio"
260
- )
257
+ combined += "\n\nBased on the above, identify:\n"
258
+ combined += "1. Category coverage map\n"
259
+ combined += "2. Overlapping benefits or redundant categories\n"
260
+ combined += "3. Gaps categories with no bonus multiplier\n"
261
+ combined += "4. Top 1-2 cards that would complement this portfolio"
262
+ if profile_ctx:
263
+ combined += f"\n\nUser profile context: {profile_ctx}"
264
+ combined += "\nTailor the gap analysis and recommendations to this profile."
261
265
  _emit(combined, as_json, "wallet", ", ".join(card_list))
262
266
 
263
267
 
@@ -274,10 +278,20 @@ def roi(
274
278
  """First-year ROI estimate — welcome bonus + earn + credits minus annual fee."""
275
279
  from tools.brave_client import search_and_format
276
280
  from tools.credit_card_tools import _guess_cpp
281
+ import db as _db
277
282
 
278
283
  name = _read_stdin_or_arg(name)
279
284
  key = _resolve_key(api_key, no_dotenv)
280
285
 
286
+ # Pull spend values from saved profile if not provided on the command line
287
+ p = _db.get_profile()
288
+ if travel == 0.0 and p.get("travel_monthly"):
289
+ travel = float(p["travel_monthly"])
290
+ if dining == 0.0 and p.get("dining_monthly"):
291
+ dining = float(p["dining_monthly"])
292
+ if other == 0.0 and p.get("other_monthly"):
293
+ other = float(p["other_monthly"])
294
+
281
295
  annual_travel = travel * 12
282
296
  annual_dining = dining * 12
283
297
  annual_other = other * 12
@@ -310,10 +324,16 @@ def recommend(
310
324
  no_dotenv: NoDotenv = False,
311
325
  ):
312
326
  """Card recommendations matched to a spending profile and preferences."""
327
+ import db as _db
313
328
  profile = _read_stdin_or_arg(profile)
314
329
  key = _resolve_key(api_key, no_dotenv)
315
330
  pref = f"{preferences} " if preferences else ""
316
- query = f"best credit cards {profile} {pref}US 2025 site:nerdwallet.com OR site:thepointsguy.com OR site:doctorofcredit.com"
331
+
332
+ # Enrich with saved profile context if available
333
+ saved_ctx = _db.profile_as_context(cards=_db.get_card_names())
334
+ ctx = f"{saved_ctx} | " if saved_ctx else ""
335
+
336
+ query = f"best credit cards {ctx}{profile} {pref}US 2025 site:nerdwallet.com OR site:thepointsguy.com OR site:doctorofcredit.com"
317
337
  _run_search(_get_wrapper(key), query, "recommend", as_json)
318
338
 
319
339
 
@@ -465,6 +485,72 @@ def hotels(
465
485
  webbrowser.open(url)
466
486
 
467
487
 
488
+ # ---------------------------------------------------------------------------
489
+ # profile — spending profile management
490
+ # ---------------------------------------------------------------------------
491
+
492
+ profile_app = typer.Typer(name="profile", help="Manage your spending profile — used to personalise research commands.", no_args_is_help=True)
493
+ app.add_typer(profile_app)
494
+
495
+
496
+ @profile_app.command("show")
497
+ def profile_show(as_json: JsonOpt = False):
498
+ """Show your current spending profile."""
499
+ import db as _db
500
+ p = _db.get_profile()
501
+ if not p:
502
+ typer.echo("No profile set. Run: fleece profile set <field> <value>")
503
+ typer.echo(f"Fields: {', '.join(_db.PROFILE_FIELDS)}")
504
+ return
505
+ if as_json:
506
+ typer.echo(json.dumps({"command": "profile show", "profile": p, "ok": True, "error": None}))
507
+ return
508
+ for key, label in _db.PROFILE_FIELDS.items():
509
+ val = p.get(key, "")
510
+ if val:
511
+ typer.echo(f" {label:<45} {val}")
512
+
513
+
514
+ @profile_app.command("set")
515
+ def profile_set(
516
+ field: Annotated[str, typer.Argument(help=f"Field name. One of: {', '.join(__import__('db').PROFILE_FIELDS)}")],
517
+ value: Annotated[str, typer.Argument(help="Value to set.")],
518
+ as_json: JsonOpt = False,
519
+ ):
520
+ """Set a profile field."""
521
+ import db as _db
522
+ try:
523
+ _db.set_profile_field(field, value)
524
+ except ValueError as e:
525
+ _error_exit(str(e), code=1)
526
+ if as_json:
527
+ typer.echo(json.dumps({"command": "profile set", "field": field, "value": value, "ok": True, "error": None}))
528
+ else:
529
+ typer.echo(f"Profile updated: {field} = {value}")
530
+
531
+
532
+ @profile_app.command("unset")
533
+ def profile_unset(
534
+ field: Annotated[str, typer.Argument(help="Field name to clear.")],
535
+ as_json: JsonOpt = False,
536
+ ):
537
+ """Clear a single profile field."""
538
+ import db as _db
539
+ _db.clear_profile_field(field)
540
+ if as_json:
541
+ typer.echo(json.dumps({"command": "profile unset", "field": field, "ok": True, "error": None}))
542
+ else:
543
+ typer.echo(f"Cleared: {field}")
544
+
545
+
546
+ @profile_app.command("fields")
547
+ def profile_fields():
548
+ """List all available profile fields and their descriptions."""
549
+ import db as _db
550
+ for key, label in _db.PROFILE_FIELDS.items():
551
+ typer.echo(f" {key:<30} {label}")
552
+
553
+
468
554
  # ---------------------------------------------------------------------------
469
555
  # cards — profile management (read/write user_cards.json)
470
556
  # ---------------------------------------------------------------------------
@@ -23,8 +23,26 @@ CREATE TABLE IF NOT EXISTS cards (
23
23
  image_url TEXT NOT NULL DEFAULT '',
24
24
  date_added TEXT NOT NULL DEFAULT (date('now'))
25
25
  );
26
+ CREATE TABLE IF NOT EXISTS profile (
27
+ key TEXT PRIMARY KEY,
28
+ value TEXT NOT NULL DEFAULT ''
29
+ );
26
30
  """
27
31
 
32
+ # Default profile fields and their human-readable labels
33
+ PROFILE_FIELDS = {
34
+ "dining_monthly": "Monthly dining spend ($)",
35
+ "groceries_monthly": "Monthly groceries spend ($)",
36
+ "travel_monthly": "Monthly travel spend ($)",
37
+ "gas_monthly": "Monthly gas spend ($)",
38
+ "other_monthly": "Monthly other spend ($)",
39
+ "annual_fee_tolerance": "Max annual fee willing to pay ($)",
40
+ "points_programs": "Preferred points programs (e.g. Amex MR, Chase UR)",
41
+ "home_airport": "Home airport IATA code (e.g. JFK)",
42
+ "goal": "Current redemption goal (e.g. business class to Tokyo)",
43
+ "preferences": "Other preferences (e.g. no foreign transaction fees)",
44
+ }
45
+
28
46
  _COLUMNS = ("id", "name", "last_four", "annual_fee", "credit_limit",
29
47
  "rewards", "expiration", "image_url", "date_added")
30
48
 
@@ -133,6 +151,73 @@ def remove_card(name: str) -> bool:
133
151
  return cur.rowcount > 0
134
152
 
135
153
 
154
+ # ---------------------------------------------------------------------------
155
+ # Profile
156
+ # ---------------------------------------------------------------------------
157
+
158
+ def get_profile() -> dict[str, str]:
159
+ """Return the full profile as {key: value}. Missing keys return ''."""
160
+ init_db()
161
+ with _conn() as con:
162
+ rows = con.execute("SELECT key, value FROM profile").fetchall()
163
+ return {r["key"]: r["value"] for r in rows}
164
+
165
+
166
+ def set_profile_field(key: str, value: str) -> None:
167
+ """Upsert a single profile field."""
168
+ if key not in PROFILE_FIELDS:
169
+ raise ValueError(f'Unknown profile field "{key}". Valid fields: {", ".join(PROFILE_FIELDS)}')
170
+ init_db()
171
+ with _conn() as con:
172
+ con.execute(
173
+ "INSERT INTO profile (key, value) VALUES (?, ?) "
174
+ "ON CONFLICT(key) DO UPDATE SET value = excluded.value",
175
+ (key, value),
176
+ )
177
+
178
+
179
+ def clear_profile_field(key: str) -> None:
180
+ """Remove a single profile field."""
181
+ init_db()
182
+ with _conn() as con:
183
+ con.execute("DELETE FROM profile WHERE key = ?", (key,))
184
+
185
+
186
+ def profile_as_context(cards: list[str] | None = None) -> str:
187
+ """
188
+ Return a compact natural-language summary of the user profile suitable
189
+ for injecting into a Brave Search query or LLM prompt.
190
+ """
191
+ p = get_profile()
192
+ parts = []
193
+
194
+ spend_fields = [
195
+ ("dining_monthly", "dining"),
196
+ ("groceries_monthly", "groceries"),
197
+ ("travel_monthly", "travel"),
198
+ ("gas_monthly", "gas"),
199
+ ("other_monthly", "other"),
200
+ ]
201
+ spend_parts = [f"${p[k]}/mo {label}" for k, label in spend_fields if p.get(k)]
202
+ if spend_parts:
203
+ parts.append("Spending: " + ", ".join(spend_parts))
204
+
205
+ if p.get("annual_fee_tolerance"):
206
+ parts.append(f"Max annual fee: ${p['annual_fee_tolerance']}")
207
+ if p.get("points_programs"):
208
+ parts.append(f"Points programs: {p['points_programs']}")
209
+ if p.get("home_airport"):
210
+ parts.append(f"Home airport: {p['home_airport']}")
211
+ if p.get("goal"):
212
+ parts.append(f"Goal: {p['goal']}")
213
+ if p.get("preferences"):
214
+ parts.append(f"Preferences: {p['preferences']}")
215
+ if cards:
216
+ parts.append(f"Current cards: {', '.join(cards)}")
217
+
218
+ return " | ".join(parts) if parts else ""
219
+
220
+
136
221
  # ---------------------------------------------------------------------------
137
222
  # Migration
138
223
  # ---------------------------------------------------------------------------
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fleece-cli"
7
- version = "0.3.1"
7
+ version = "0.4.0"
8
8
  description = "Credit card research and redemption CLI — live data via Brave Search, PointsYeah URL generation, designed for humans and AI agents."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,49 +0,0 @@
1
- # Fleece - Credit Card Research Chatbot
2
-
3
- ## Project Overview
4
- **Fleece** is a Streamlit-based conversational AI application designed to help users find the best credit cards for their needs. The tagline is "Find the best card for deal saviors."
5
-
6
- ### Core Purpose
7
- Fleece is a chatbot that:
8
- - Uses OpenAI's chat models (gpt-3.5-turbo, gpt-4, gpt-4-turbo, gpt-4o) to provide conversational credit card research and recommendations
9
- - Maintains entity-based memory across conversations to remember user details and preferences
10
- - Allows users to explore available credit cards and manage their existing card portfolio
11
- - Stores conversation history and provides download capability
12
-
13
- ### Tech Stack
14
- - **Frontend**: Streamlit (Python web app framework)
15
- - **LLM**: OpenAI ChatGPT (via LangChain)
16
- - **Memory**: ConversationEntityMemory (tracks entities mentioned in conversation)
17
- - **Language**: Python
18
-
19
- ## Key Features
20
- 1. **Conversational Interface**: Chat with an AI assistant about credit cards
21
- 2. **Entity Memory**: Remembers details about the user across conversation turns
22
- 3. **Multi-model Support**: Choose between different OpenAI models
23
- 4. **Conversation Management**:
24
- - Start new chats
25
- - Browse past conversation sessions
26
- - Download conversation history
27
- - Customize memory buffer size (K parameter)
28
- 5. **Navigation**: Planned pages for "Credit Cards" exploration and "My Credit Cards" management
29
-
30
- ## Current Limitations & Opportunities
31
- - **Limited knowledge**: Currently relies on OpenAI's training data, which may be outdated for credit card details
32
- - **No specialized tools**: The chatbot cannot search current issuer websites or real-time data sources
33
- - **No structured research**: Lacks the ability to provide specific, verified credit card information (fees, benefits, offers, transfer partners, etc.)
34
-
35
- ## Future Integration Opportunities
36
- - **Multi-page app expansion**: Build out the "Credit Cards" discovery page and "My Credit Cards" portfolio management
37
- - **Database backend**: Store user profiles, card portfolios, and preferences
38
-
39
- ## CLI — Redemption Commands
40
- The `fleece flights` and `fleece hotels` commands are merged from the former `pointsyeah-cli` project (now archived). They generate PointsYeah search URLs with no API key required — pure stdlib, zero external dependencies. Source lives in `pointsyeah.py`.
41
-
42
- ## Development Notes
43
- - Author: Yuan Chen
44
- - Created: March 16, 2025
45
- - Uses custom CSS styling (style.css)
46
- - Requires OpenAI API key (entered via sidebar, not stored)
47
-
48
- ## Infrastructure Notes
49
- - **Databricks**: No Databricks resources are available at the moment. The Databricks App deployment workflow has been removed. Do not suggest or implement Databricks-based solutions until resources are provisioned.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes