ray-finance 0.1.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.
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/ai/agent.d.ts +2 -0
- package/dist/ai/agent.js +93 -0
- package/dist/ai/audit.d.ts +3 -0
- package/dist/ai/audit.js +6 -0
- package/dist/ai/context.d.ts +6 -0
- package/dist/ai/context.js +93 -0
- package/dist/ai/insights.d.ts +3 -0
- package/dist/ai/insights.js +401 -0
- package/dist/ai/memory.d.ts +14 -0
- package/dist/ai/memory.js +12 -0
- package/dist/ai/redactor.d.ts +2 -0
- package/dist/ai/redactor.js +103 -0
- package/dist/ai/system-prompt.d.ts +2 -0
- package/dist/ai/system-prompt.js +85 -0
- package/dist/ai/tools.d.ts +4 -0
- package/dist/ai/tools.js +699 -0
- package/dist/alerts/index.d.ts +11 -0
- package/dist/alerts/index.js +95 -0
- package/dist/auth/anthropic.d.ts +7 -0
- package/dist/auth/anthropic.js +85 -0
- package/dist/auth/pkce.d.ts +5 -0
- package/dist/auth/pkce.js +10 -0
- package/dist/auth/store.d.ts +12 -0
- package/dist/auth/store.js +51 -0
- package/dist/cli/backup.d.ts +2 -0
- package/dist/cli/backup.js +94 -0
- package/dist/cli/chat.d.ts +1 -0
- package/dist/cli/chat.js +203 -0
- package/dist/cli/commands.d.ts +13 -0
- package/dist/cli/commands.js +201 -0
- package/dist/cli/format.d.ts +14 -0
- package/dist/cli/format.js +144 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +186 -0
- package/dist/cli/scheduler.d.ts +2 -0
- package/dist/cli/scheduler.js +114 -0
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +174 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +60 -0
- package/dist/daily-sync.d.ts +7 -0
- package/dist/daily-sync.js +109 -0
- package/dist/db/connection.d.ts +5 -0
- package/dist/db/connection.js +45 -0
- package/dist/db/encryption.d.ts +3 -0
- package/dist/db/encryption.js +35 -0
- package/dist/db/helpers.d.ts +16 -0
- package/dist/db/helpers.js +45 -0
- package/dist/db/schema.d.ts +2 -0
- package/dist/db/schema.js +199 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plaid/client.d.ts +2 -0
- package/dist/plaid/client.js +22 -0
- package/dist/plaid/link.d.ts +8 -0
- package/dist/plaid/link.js +23 -0
- package/dist/plaid/sync.d.ts +18 -0
- package/dist/plaid/sync.js +186 -0
- package/dist/public/favicon.png +0 -0
- package/dist/public/link.html +184 -0
- package/dist/public/ray-logo-dark.png +0 -0
- package/dist/queries/index.d.ts +163 -0
- package/dist/queries/index.js +411 -0
- package/dist/scoring/index.d.ts +53 -0
- package/dist/scoring/index.js +375 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +172 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clark Dinnison
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src=".github/ray-logo.png" alt="Ray" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
An open-source CLI that connects to your bank and already knows your finances before you ask.
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/ray-finance"><img src="https://img.shields.io/npm/v/ray-finance.svg" alt="npm version" /></a>
|
|
11
|
+
<a href="https://github.com/cdinnison/ray-finance/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License" /></a>
|
|
12
|
+
<a href="https://github.com/cdinnison/ray-finance/stargazers"><img src="https://img.shields.io/github/stars/cdinnison/ray-finance.svg?style=social" alt="GitHub stars" /></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<br />
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Friday, Mar 28
|
|
19
|
+
|
|
20
|
+
net worth $45,230 +$120
|
|
21
|
+
|
|
22
|
+
spending $2,340 this month · $340 less vs last month
|
|
23
|
+
Dining -$114 · Shopping -$142 · Groceries -$73
|
|
24
|
+
|
|
25
|
+
▓▓▓▓▓▓▓░ Dining 92%
|
|
26
|
+
|
|
27
|
+
▓▓▓▓░░░░ Emergency fund $18,200/$40,000
|
|
28
|
+
|
|
29
|
+
upcoming Netflix $16 in 3d · Comcast $142 in 6d
|
|
30
|
+
|
|
31
|
+
score 72/100 · 5d no dining · 3d on pace
|
|
32
|
+
|
|
33
|
+
──────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
❯ if I quit my job to freelance, how long can I survive?
|
|
36
|
+
|
|
37
|
+
Based on your last 3 months: you burn $4,820/mo after
|
|
38
|
+
fixed costs. With $18,200 in savings, that's
|
|
39
|
+
3.8 months of runway at current spend.
|
|
40
|
+
|
|
41
|
+
Cut dining and shopping to last-month levels and
|
|
42
|
+
you stretch to 5.1 months. Land one $8k contract
|
|
43
|
+
in that window and you never dip below $10k.
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Open Ray and it shows your net worth, spending vs last month, budget pacing, and upcoming bills — before you type a word. Ask a question and it answers from your real data, not guesses. Local-first. Encrypted. Open source.
|
|
47
|
+
|
|
48
|
+
## Features
|
|
49
|
+
|
|
50
|
+
- **It already knows** — Every conversation starts with a real-time financial briefing. Net worth, spending velocity, budget alerts, goal pace, upcoming bills, and your daily score. No "let me look that up."
|
|
51
|
+
- **Bank sync via Plaid** — Connect checking, savings, credit cards, investments, and loans
|
|
52
|
+
- **Encrypted local database** — All data stays on your machine in an AES-256 encrypted SQLite database
|
|
53
|
+
- **Daily scoring** — A 0-100 behavior score with streaks and 14 unlockable achievements. No restaurants for a week? That's Kitchen Hero. Five zero-spend days? Monk Mode.
|
|
54
|
+
- **CFO personality** — Ray doesn't list options. It tells you what it would do and why, references your goals, and flags problems you haven't noticed yet.
|
|
55
|
+
- **Budgets and goals** — Track spending limits by category and progress toward financial goals
|
|
56
|
+
- **PII masking** — Names, account numbers, and identifying details are scrubbed before anything reaches the AI. Your data is analyzed, not exposed.
|
|
57
|
+
- **Smart alerts** — Large transactions, low balances, budget overruns
|
|
58
|
+
- **Auto-recategorization** — Define rules to automatically re-label transactions
|
|
59
|
+
- **Scheduled daily sync** — Automatic bank sync via launchd (macOS) or cron (Linux)
|
|
60
|
+
- **Export/import** — Back up and restore your financial data
|
|
61
|
+
|
|
62
|
+
## Install
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install -g ray-finance
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ray setup
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The setup wizard offers two modes:
|
|
75
|
+
|
|
76
|
+
### Quick setup (managed)
|
|
77
|
+
|
|
78
|
+
We handle the API keys. Your data stays local. $10/mo.
|
|
79
|
+
|
|
80
|
+
1. Enter your name
|
|
81
|
+
2. Get a Ray API key (opens Stripe checkout)
|
|
82
|
+
3. Link your accounts — checking, savings, credit cards, investments, loans, mortgage
|
|
83
|
+
4. Done — daily sync auto-scheduled at 6am
|
|
84
|
+
|
|
85
|
+
### Self-hosted
|
|
86
|
+
|
|
87
|
+
Bring your own Anthropic and Plaid credentials. Free forever.
|
|
88
|
+
|
|
89
|
+
1. Enter your Anthropic API key ([get one](https://console.anthropic.com))
|
|
90
|
+
2. Enter your Plaid credentials ([get free keys](https://dashboard.plaid.com/signup))
|
|
91
|
+
3. Link your accounts — checking, savings, credit cards, investments, loans, mortgage
|
|
92
|
+
4. Done
|
|
93
|
+
|
|
94
|
+
## Commands
|
|
95
|
+
|
|
96
|
+
Run `ray --help` to see all available commands.
|
|
97
|
+
|
|
98
|
+
| Command | Description |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| `ray` | Interactive AI chat with your financial advisor |
|
|
101
|
+
| `ray setup` | Configure API keys and preferences |
|
|
102
|
+
| `ray link` | Connect a new bank account |
|
|
103
|
+
| `ray sync` | Pull latest transactions and balances |
|
|
104
|
+
| `ray status` | Quick financial dashboard |
|
|
105
|
+
| `ray transactions` | Recent transactions (filterable by category, merchant) |
|
|
106
|
+
| `ray spending [period]` | Spending breakdown by category |
|
|
107
|
+
| `ray budgets` | Budget status and overruns |
|
|
108
|
+
| `ray goals` | Financial goal progress |
|
|
109
|
+
| `ray score` | Daily score, streaks, and achievements |
|
|
110
|
+
| `ray alerts` | Active financial alerts |
|
|
111
|
+
| `ray export [path]` | Export data to a backup file |
|
|
112
|
+
| `ray import <path>` | Restore from a backup file |
|
|
113
|
+
| `ray billing` | Manage your Ray subscription (managed mode only) |
|
|
114
|
+
|
|
115
|
+
## How It Works
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
Checking · Savings · Credit · Investments · Loans · Mortgage
|
|
119
|
+
│
|
|
120
|
+
Plaid API
|
|
121
|
+
│
|
|
122
|
+
┌──────────▼──────────┐
|
|
123
|
+
│ Local SQLite DB │
|
|
124
|
+
│ (AES-256 encrypted) │
|
|
125
|
+
└──────────┬──────────┘
|
|
126
|
+
│
|
|
127
|
+
┌──────────▼──────────┐
|
|
128
|
+
│ ray CLI │
|
|
129
|
+
│ insights · tools │
|
|
130
|
+
│ scoring · alerts │
|
|
131
|
+
└──────────┬──────────┘
|
|
132
|
+
│
|
|
133
|
+
Claude API
|
|
134
|
+
(PII-masked)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Two outbound calls: Plaid (bank sync) and Anthropic (AI chat, PII-masked). Your financial data is never stored off your machine. No telemetry. No analytics.
|
|
138
|
+
|
|
139
|
+
## Security & Privacy
|
|
140
|
+
|
|
141
|
+
- All financial data stored locally in `~/.ray/data/finance.db`
|
|
142
|
+
- Database encrypted with AES-256 (SQLCipher)
|
|
143
|
+
- Plaid access tokens encrypted at rest with AES-256-GCM
|
|
144
|
+
- Config file stored with `0600` permissions
|
|
145
|
+
- PII redacted before sending to Claude API
|
|
146
|
+
- No data leaves your machine — only API calls to Plaid and Anthropic
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
Ray stores everything in `~/.ray/`:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
~/.ray/
|
|
154
|
+
config.json # API keys and preferences (0600 permissions)
|
|
155
|
+
context.md # Persistent financial context for AI
|
|
156
|
+
data/
|
|
157
|
+
finance.db # Encrypted SQLite database
|
|
158
|
+
sync.log # Daily sync output
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Environment Variables
|
|
162
|
+
|
|
163
|
+
You can also configure Ray via environment variables or a `.env` file:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
ANTHROPIC_API_KEY= # Anthropic API key for AI chat
|
|
167
|
+
PLAID_CLIENT_ID= # Plaid client ID
|
|
168
|
+
PLAID_SECRET= # Plaid secret key
|
|
169
|
+
PLAID_ENV=production # Plaid environment
|
|
170
|
+
DB_ENCRYPTION_KEY= # Database encryption key
|
|
171
|
+
PLAID_TOKEN_SECRET= # Key for encrypting stored Plaid tokens
|
|
172
|
+
RAY_API_KEY= # Ray API key (managed mode, replaces the above)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Roadmap
|
|
176
|
+
|
|
177
|
+
- [ ] Daily digest email — morning summary of your finances
|
|
178
|
+
|
|
179
|
+
Have an idea? [Open a PR](https://github.com/cdinnison/ray-finance/pulls).
|
|
180
|
+
|
|
181
|
+
## Contributing
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git clone https://github.com/cdinnison/ray-finance.git
|
|
185
|
+
cd ray-finance
|
|
186
|
+
npm install
|
|
187
|
+
npm run build
|
|
188
|
+
npm link # Makes 'ray' available globally
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
PRs welcome. Please open an issue first for large changes.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
[MIT](LICENSE)
|
package/dist/ai/agent.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { config, useManaged, RAY_PROXY_BASE } from "../config.js";
|
|
3
|
+
import { buildSystemPrompt } from "./system-prompt.js";
|
|
4
|
+
import { toolDefinitions, executeTool } from "./tools.js";
|
|
5
|
+
import { getConversationHistory, saveMessage } from "./memory.js";
|
|
6
|
+
import { logToolCall } from "./audit.js";
|
|
7
|
+
import { redact, unredact } from "./redactor.js";
|
|
8
|
+
const anthropic = new Anthropic(useManaged()
|
|
9
|
+
? { apiKey: config.rayApiKey, baseURL: `${RAY_PROXY_BASE}/ai` }
|
|
10
|
+
: { apiKey: config.anthropicKey });
|
|
11
|
+
function supportsThinking(model) {
|
|
12
|
+
return /sonnet-4|opus-4/i.test(model);
|
|
13
|
+
}
|
|
14
|
+
export async function handleMessage(db, userMessage) {
|
|
15
|
+
// Save incoming message
|
|
16
|
+
saveMessage(db, "user", userMessage);
|
|
17
|
+
// Load conversation context, truncated to fit token budget
|
|
18
|
+
const rawHistory = getConversationHistory(db, 30);
|
|
19
|
+
const MAX_HISTORY_CHARS = 24_000; // ~6k tokens, leaves room for system prompt + response
|
|
20
|
+
let historyChars = 0;
|
|
21
|
+
const history = [];
|
|
22
|
+
for (let i = rawHistory.length - 1; i >= 0; i--) {
|
|
23
|
+
historyChars += rawHistory[i].content.length;
|
|
24
|
+
if (historyChars > MAX_HISTORY_CHARS)
|
|
25
|
+
break;
|
|
26
|
+
history.unshift(rawHistory[i]);
|
|
27
|
+
}
|
|
28
|
+
// Build system prompt and redact PII before sending to API
|
|
29
|
+
const systemPrompt = redact(buildSystemPrompt(db));
|
|
30
|
+
// Build messages array from history, redacting PII
|
|
31
|
+
const messages = history.map(h => ({
|
|
32
|
+
role: h.role,
|
|
33
|
+
content: redact(h.content),
|
|
34
|
+
}));
|
|
35
|
+
// Ensure last message is the current user message
|
|
36
|
+
if (messages.length === 0 || messages[messages.length - 1].content !== userMessage) {
|
|
37
|
+
messages.push({ role: "user", content: redact(userMessage) });
|
|
38
|
+
}
|
|
39
|
+
// Extended thinking config
|
|
40
|
+
const useThinking = config.thinkingBudget > 0 && supportsThinking(config.model);
|
|
41
|
+
try {
|
|
42
|
+
// Build API params
|
|
43
|
+
const apiParams = {
|
|
44
|
+
model: config.model,
|
|
45
|
+
max_tokens: useThinking ? 16000 : 4096,
|
|
46
|
+
system: systemPrompt,
|
|
47
|
+
tools: toolDefinitions,
|
|
48
|
+
messages,
|
|
49
|
+
};
|
|
50
|
+
if (useThinking) {
|
|
51
|
+
apiParams.thinking = {
|
|
52
|
+
type: "enabled",
|
|
53
|
+
budget_tokens: config.thinkingBudget,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Initial API call
|
|
57
|
+
let response = await anthropic.messages.create(apiParams);
|
|
58
|
+
// Agentic tool loop
|
|
59
|
+
while (response.stop_reason === "tool_use") {
|
|
60
|
+
// Filter out thinking blocks before adding to messages
|
|
61
|
+
const assistantContent = response.content.filter((b) => b.type !== "thinking");
|
|
62
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
63
|
+
const toolResults = [];
|
|
64
|
+
for (const block of assistantContent) {
|
|
65
|
+
if (block.type === "tool_use") {
|
|
66
|
+
const result = await executeTool(db, block.name, block.input);
|
|
67
|
+
logToolCall(db, block.name, block.input, result, response.usage?.output_tokens);
|
|
68
|
+
toolResults.push({
|
|
69
|
+
type: "tool_result",
|
|
70
|
+
tool_use_id: block.id,
|
|
71
|
+
content: redact(result),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
messages.push({ role: "user", content: toolResults });
|
|
76
|
+
response = await anthropic.messages.create(apiParams);
|
|
77
|
+
}
|
|
78
|
+
// Extract text response (filter out thinking blocks), restore PII for display
|
|
79
|
+
const textBlocks = response.content.filter((b) => b.type === "text");
|
|
80
|
+
const responseText = unredact(textBlocks.map((b) => b.text).join("\n"));
|
|
81
|
+
// Save assistant response
|
|
82
|
+
saveMessage(db, "assistant", responseText);
|
|
83
|
+
return responseText || "I looked into that but couldn't formulate a response. Could you try rephrasing?";
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Log full error internally but don't expose details to user
|
|
87
|
+
const safeMessage = error.status
|
|
88
|
+
? `API error (${error.status})`
|
|
89
|
+
: "internal error";
|
|
90
|
+
console.error("AI agent error:", safeMessage);
|
|
91
|
+
return "Sorry, I had trouble processing that. Could you try again?";
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/ai/audit.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function logToolCall(db, toolName, inputParams, resultSummary, tokensUsed) {
|
|
2
|
+
db.prepare(`INSERT INTO ai_audit_log (tool_name, input_params, result_summary, tokens_used) VALUES (?, ?, ?, ?)`).run(toolName, JSON.stringify(inputParams), resultSummary.slice(0, 500), tokensUsed || null);
|
|
3
|
+
}
|
|
4
|
+
export function getAuditLog(db, limit = 50) {
|
|
5
|
+
return db.prepare(`SELECT * FROM ai_audit_log ORDER BY id DESC LIMIT ?`).all(limit);
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function getContextPath(): string;
|
|
2
|
+
export declare function readContext(): string;
|
|
3
|
+
export declare function writeContext(content: string): void;
|
|
4
|
+
export declare function isContextEmpty(): boolean;
|
|
5
|
+
export declare function replaceContextSection(section: string, content: string): void;
|
|
6
|
+
export declare function createContextTemplate(userName: string): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
const CONTEXT_PATH = resolve(homedir(), ".ray", "context.md");
|
|
5
|
+
export function getContextPath() {
|
|
6
|
+
return CONTEXT_PATH;
|
|
7
|
+
}
|
|
8
|
+
export function readContext() {
|
|
9
|
+
if (!existsSync(CONTEXT_PATH))
|
|
10
|
+
return "";
|
|
11
|
+
try {
|
|
12
|
+
return readFileSync(CONTEXT_PATH, "utf-8");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function writeContext(content) {
|
|
19
|
+
const dir = resolve(homedir(), ".ray");
|
|
20
|
+
if (!existsSync(dir))
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
writeFileSync(CONTEXT_PATH, content, { encoding: "utf-8", mode: 0o600 });
|
|
23
|
+
try {
|
|
24
|
+
chmodSync(CONTEXT_PATH, 0o600);
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
export function isContextEmpty() {
|
|
29
|
+
const content = readContext();
|
|
30
|
+
if (!content || content.trim().length === 0)
|
|
31
|
+
return true;
|
|
32
|
+
// Check if it still has placeholder text (template hasn't been filled)
|
|
33
|
+
const placeholders = ["(Add income sources", "(Linked accounts will appear", "(Add financial goals", "(Add current financial strategy", "(Log important financial decisions", "(Track action items"];
|
|
34
|
+
const filledSections = placeholders.filter(p => !content.includes(p)).length;
|
|
35
|
+
// Consider empty if most sections still have placeholder text
|
|
36
|
+
return filledSections < 2;
|
|
37
|
+
}
|
|
38
|
+
export function replaceContextSection(section, content) {
|
|
39
|
+
let current = readContext();
|
|
40
|
+
if (!current) {
|
|
41
|
+
writeContext(`## ${section}\n${content}\n`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const sectionHeader = `## ${section}`;
|
|
45
|
+
const sectionIdx = current.indexOf(sectionHeader);
|
|
46
|
+
if (sectionIdx !== -1) {
|
|
47
|
+
// Find the next ## heading after this section
|
|
48
|
+
const afterHeader = sectionIdx + sectionHeader.length;
|
|
49
|
+
const nextSectionIdx = current.indexOf("\n## ", afterHeader);
|
|
50
|
+
const before = current.slice(0, afterHeader);
|
|
51
|
+
const after = nextSectionIdx !== -1 ? current.slice(nextSectionIdx) : "";
|
|
52
|
+
current = `${before}\n${content}\n${after}`;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Insert before ## Open Items if it exists, otherwise append
|
|
56
|
+
const openItemsIdx = current.indexOf("## Open Items");
|
|
57
|
+
if (openItemsIdx !== -1) {
|
|
58
|
+
current = `${current.slice(0, openItemsIdx)}${sectionHeader}\n${content}\n\n${current.slice(openItemsIdx)}`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
current = `${current.trimEnd()}\n\n${sectionHeader}\n${content}\n`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
writeContext(current);
|
|
65
|
+
}
|
|
66
|
+
export function createContextTemplate(userName) {
|
|
67
|
+
if (existsSync(CONTEXT_PATH))
|
|
68
|
+
return; // don't overwrite existing
|
|
69
|
+
const template = `# Financial Context for ${userName}
|
|
70
|
+
|
|
71
|
+
## Family
|
|
72
|
+
- ${userName}
|
|
73
|
+
|
|
74
|
+
## Income
|
|
75
|
+
- (Add income sources and amounts)
|
|
76
|
+
|
|
77
|
+
## Accounts
|
|
78
|
+
- (Linked accounts will appear after syncing)
|
|
79
|
+
|
|
80
|
+
## Goals
|
|
81
|
+
- (Add financial goals)
|
|
82
|
+
|
|
83
|
+
## Strategy
|
|
84
|
+
- (Add current financial strategy and priorities)
|
|
85
|
+
|
|
86
|
+
## Key Decisions
|
|
87
|
+
- (Log important financial decisions here)
|
|
88
|
+
|
|
89
|
+
## Open Items
|
|
90
|
+
- (Track action items and follow-ups)
|
|
91
|
+
`;
|
|
92
|
+
writeContext(template);
|
|
93
|
+
}
|