plasalid 0.5.1 → 0.5.2
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 +4 -2
- package/dist/cli/chat.js +50 -12
- package/dist/cli/format.js +4 -1
- package/dist/cli/ink/hooks/useFooterText.js +13 -8
- package/dist/cli/logo.js +2 -17
- package/dist/cli/quotes.d.ts +15 -0
- package/dist/cli/quotes.js +90 -0
- package/dist/cli/setup.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<h1 align="center"><code><°(((><</code></h1>
|
|
2
|
+
|
|
1
3
|
<h1 align="center">Plasalid</h1>
|
|
2
4
|
|
|
3
5
|
<p align="center">
|
|
@@ -100,7 +102,7 @@ plasalid review [--dry-run] # Connect related transactions, learn recurr
|
|
|
100
102
|
│ ~/.plasalid/data/ │
|
|
101
103
|
└──────────┬──────────┘
|
|
102
104
|
│
|
|
103
|
-
|
|
105
|
+
plasalid scan / plasalid record
|
|
104
106
|
│
|
|
105
107
|
Claude API (PII-redacted)
|
|
106
108
|
│
|
|
@@ -108,7 +110,7 @@ plasalid review [--dry-run] # Connect related transactions, learn recurr
|
|
|
108
110
|
│ Encrypted DB │◀──── plasalid review
|
|
109
111
|
└──────────┬──────────┘
|
|
110
112
|
│
|
|
111
|
-
|
|
113
|
+
plasalid
|
|
112
114
|
```
|
|
113
115
|
|
|
114
116
|
Two outbound calls: the AI provider during scan, and the AI provider during chat. Both are PII-redacted. Your financial data is never stored off your machine. No telemetry. No analytics.
|
package/dist/cli/chat.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import {
|
|
3
|
-
import { banner } from "./format.js";
|
|
4
|
-
import { printLogo } from "./logo.js";
|
|
2
|
+
import { pickQuote } from "./quotes.js";
|
|
5
3
|
export async function startChat() {
|
|
6
4
|
const { getDb } = await import("../db/connection.js");
|
|
7
5
|
const db = getDb();
|
|
8
6
|
console.log("");
|
|
9
|
-
|
|
10
|
-
console.log("");
|
|
11
|
-
console.log(banner());
|
|
7
|
+
renderQuote(pickQuote());
|
|
12
8
|
console.log("");
|
|
13
9
|
const accountCount = db
|
|
14
10
|
.prepare(`SELECT COUNT(*) AS n FROM accounts`)
|
|
@@ -16,13 +12,55 @@ export async function startChat() {
|
|
|
16
12
|
if (accountCount.n === 0) {
|
|
17
13
|
console.log(chalk.yellow("No accounts scanned yet. Run `plasalid data` to drop your bank/credit card statements in, then run `plasalid scan`."));
|
|
18
14
|
console.log("");
|
|
19
|
-
console.log(chalk.dim("You can still chat, but I won't have any data to answer questions about."));
|
|
20
|
-
console.log("");
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
console.log(chalk.dim(`Hi ${config.userName}. Ask me anything about your financial data.`));
|
|
24
|
-
console.log("");
|
|
25
15
|
}
|
|
16
|
+
console.log(chalk.dim("Ready when you are. Ask Plasalid anything about your money."));
|
|
17
|
+
console.log("");
|
|
26
18
|
const { runChatApp } = await import("./ink/mount.js");
|
|
27
19
|
await runChatApp({ db });
|
|
28
20
|
}
|
|
21
|
+
const MAX_COL_WIDTH = 80;
|
|
22
|
+
const MAX_WRAP_WIDTH = 56;
|
|
23
|
+
const MIN_WRAP_WIDTH = 24;
|
|
24
|
+
const MAX_LEFT_PAD = 4;
|
|
25
|
+
function renderQuote(q) {
|
|
26
|
+
const cols = Math.min(process.stdout.columns || MAX_COL_WIDTH, MAX_COL_WIDTH);
|
|
27
|
+
const wrapWidth = Math.min(MAX_WRAP_WIDTH, Math.max(MIN_WRAP_WIDTH, cols - 8));
|
|
28
|
+
const lines = wrapText(q.text, wrapWidth);
|
|
29
|
+
const decorated = lines.map((line, i) => decorateQuote(line, i, lines.length));
|
|
30
|
+
const blockWidth = Math.max(...decorated.map((l) => l.length));
|
|
31
|
+
const centeredPad = Math.max(0, Math.floor((cols - blockWidth) / 2));
|
|
32
|
+
const leftPad = Math.min(MAX_LEFT_PAD, centeredPad);
|
|
33
|
+
for (const line of decorated) {
|
|
34
|
+
console.log(chalk.dim(" ".repeat(leftPad) + line));
|
|
35
|
+
}
|
|
36
|
+
console.log("");
|
|
37
|
+
const author = `— ${q.author}`;
|
|
38
|
+
const authorPad = Math.max(leftPad, leftPad + blockWidth - author.length);
|
|
39
|
+
console.log(chalk.dim(" ".repeat(authorPad) + author));
|
|
40
|
+
console.log("");
|
|
41
|
+
}
|
|
42
|
+
function decorateQuote(line, index, total) {
|
|
43
|
+
const open = index === 0 ? "“" : " ";
|
|
44
|
+
const close = index === total - 1 ? "”" : "";
|
|
45
|
+
return `${open}${line}${close}`;
|
|
46
|
+
}
|
|
47
|
+
function wrapText(text, width) {
|
|
48
|
+
const words = text.split(/\s+/);
|
|
49
|
+
const lines = [];
|
|
50
|
+
let current = "";
|
|
51
|
+
for (const word of words) {
|
|
52
|
+
if (!current) {
|
|
53
|
+
current = word;
|
|
54
|
+
}
|
|
55
|
+
else if (current.length + 1 + word.length <= width) {
|
|
56
|
+
current += " " + word;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
lines.push(current);
|
|
60
|
+
current = word;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (current)
|
|
64
|
+
lines.push(current);
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
package/dist/cli/format.js
CHANGED
|
@@ -54,8 +54,9 @@ export function formatError(error, context) {
|
|
|
54
54
|
return `${chalk.red("✗")} ${context ? context + ": " : ""}${safeMsg}`;
|
|
55
55
|
}
|
|
56
56
|
export function banner() {
|
|
57
|
-
return chalk.bold("Plasalid") + chalk.dim(" · The Harness Layer for Personal Finance");
|
|
57
|
+
return chalk.cyan("<°(((>< ") + chalk.bold("Plasalid") + chalk.dim(" · The Harness Layer for Personal Finance");
|
|
58
58
|
}
|
|
59
|
+
const FISH_ART = "<°(((><";
|
|
59
60
|
function stripAnsi(str) {
|
|
60
61
|
return str.replace(ANSI_RE, "");
|
|
61
62
|
}
|
|
@@ -74,6 +75,8 @@ function box(label, lines) {
|
|
|
74
75
|
const DISCLAIMER = "Plasalid is an assistant, not a financial advisor. It only summarizes financial statements — verify amounts against your statements before relying on them.";
|
|
75
76
|
export function helpScreen(commands) {
|
|
76
77
|
const sections = [
|
|
78
|
+
chalk.cyan(FISH_ART),
|
|
79
|
+
"",
|
|
77
80
|
banner(),
|
|
78
81
|
"",
|
|
79
82
|
box("Usage", [
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
const HINTS = [
|
|
4
|
-
"try: what
|
|
5
|
-
"try:
|
|
6
|
-
"try:
|
|
7
|
-
"try:
|
|
8
|
-
"try:
|
|
9
|
-
"try:
|
|
10
|
-
"try:
|
|
4
|
+
"try: what's my net worth?",
|
|
5
|
+
"try: where did my money go last month?",
|
|
6
|
+
"try: biggest expense this month?",
|
|
7
|
+
"try: total credit card debt?",
|
|
8
|
+
"try: next card payment due?",
|
|
9
|
+
"try: list my subscriptions",
|
|
10
|
+
"try: coffee spend this year?",
|
|
11
|
+
"try: transactions over 10,000 baht",
|
|
12
|
+
"try: top merchants this quarter?",
|
|
13
|
+
"try: dining — this month vs last?",
|
|
14
|
+
"try: net worth trend this year?",
|
|
15
|
+
"try: open concerns from last scan?",
|
|
11
16
|
];
|
|
12
17
|
export function useFooterText(db) {
|
|
13
18
|
const [tick, setTick] = useState(0);
|
|
@@ -34,7 +39,7 @@ export function useFooterText(db) {
|
|
|
34
39
|
scanStr = `scanned ${Math.floor(mins / 1440)}d ago`;
|
|
35
40
|
}
|
|
36
41
|
const idx = (hintIdx + tick) % HINTS.length;
|
|
37
|
-
const parts = [`${chalk.cyan("
|
|
42
|
+
const parts = [`${chalk.cyan("<°(((><")}`];
|
|
38
43
|
if (scanStr)
|
|
39
44
|
parts.push(scanStr);
|
|
40
45
|
parts.push(HINTS[idx]);
|
package/dist/cli/logo.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
const FISH =
|
|
3
|
-
" ",
|
|
4
|
-
" ",
|
|
5
|
-
" ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑ ",
|
|
6
|
-
" ↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑ ",
|
|
7
|
-
" ↑↑↑↑↑ ↑↑↑ ↑↑↑↑↑↑ ↑↑↑↑↑↑ ",
|
|
8
|
-
" ↑↑↑↑↑↑↑↑ ↑↑↑ ↑↑↑↑↑ ↑↑↑↑↑↑ ",
|
|
9
|
-
" ↑↑↑ ↑↑ ↑↑↑ ↑↑↑↑↑↑↑ ",
|
|
10
|
-
" ↑↑↑ ↑↑↑ ↑↑↑↑↑↑↑↑↑ ",
|
|
11
|
-
" ↑↑↑↑ ↑↑↑ ↑↑↑↑↑↑↑ ↑↑↑↑↑↑↑ ",
|
|
12
|
-
" ↑↑↑↑↑↑↑ ↑↑↑ ↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑ ",
|
|
13
|
-
" ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑↑↑↑ ",
|
|
14
|
-
" ↑↑↑↑↑↑↑↑↑↑↑↑ ↑↑ ",
|
|
15
|
-
" ",
|
|
16
|
-
" ",
|
|
17
|
-
].join("\n");
|
|
2
|
+
const FISH = "<°(((><";
|
|
18
3
|
export function printLogo() {
|
|
19
|
-
console.log(chalk.
|
|
4
|
+
console.log(chalk.cyan(FISH));
|
|
20
5
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-curated quote list for the chat welcome screen.
|
|
3
|
+
*
|
|
4
|
+
* Bar: every entry is attributable to a specific public source — a book,
|
|
5
|
+
* shareholder letter, public lecture, or widely-documented quote of the named
|
|
6
|
+
* person. Well-known misattributions (Einstein on compound interest, Twain on
|
|
7
|
+
* banking, etc.) are deliberately excluded. The `source` comment beside each
|
|
8
|
+
* entry names the primary source so future maintainers can audit attribution.
|
|
9
|
+
*/
|
|
10
|
+
export interface Quote {
|
|
11
|
+
text: string;
|
|
12
|
+
author: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const QUOTES: Quote[];
|
|
15
|
+
export declare function pickQuote(): Quote;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-curated quote list for the chat welcome screen.
|
|
3
|
+
*
|
|
4
|
+
* Bar: every entry is attributable to a specific public source — a book,
|
|
5
|
+
* shareholder letter, public lecture, or widely-documented quote of the named
|
|
6
|
+
* person. Well-known misattributions (Einstein on compound interest, Twain on
|
|
7
|
+
* banking, etc.) are deliberately excluded. The `source` comment beside each
|
|
8
|
+
* entry names the primary source so future maintainers can audit attribution.
|
|
9
|
+
*/
|
|
10
|
+
export const QUOTES = [
|
|
11
|
+
// Warren Buffett — Berkshire Hathaway letters & public interviews
|
|
12
|
+
{ text: "Rule No. 1: Never lose money. Rule No. 2: Never forget rule No. 1.", author: "Warren Buffett" },
|
|
13
|
+
{ text: "Price is what you pay. Value is what you get.", author: "Warren Buffett" },
|
|
14
|
+
{ text: "Be fearful when others are greedy, and be greedy when others are fearful.", author: "Warren Buffett" },
|
|
15
|
+
{ text: "Risk comes from not knowing what you are doing.", author: "Warren Buffett" },
|
|
16
|
+
{ text: "Someone is sitting in the shade today because someone planted a tree a long time ago.", author: "Warren Buffett" },
|
|
17
|
+
{ text: "The most important investment you can make is in yourself.", author: "Warren Buffett" },
|
|
18
|
+
{ text: "If you don't find a way to make money while you sleep, you will work until you die.", author: "Warren Buffett" },
|
|
19
|
+
{ text: "Our favorite holding period is forever.", author: "Warren Buffett" },
|
|
20
|
+
// Charlie Munger — Poor Charlie's Almanack and Daily Journal meetings
|
|
21
|
+
{ text: "The first rule of compounding: never interrupt it unnecessarily.", author: "Charlie Munger" },
|
|
22
|
+
{ text: "The big money is not in the buying or the selling, but in the waiting.", author: "Charlie Munger" },
|
|
23
|
+
{ text: "Spend each day trying to be a little wiser than you were when you woke up.", author: "Charlie Munger" },
|
|
24
|
+
{ text: "It is remarkable how much long-term advantage people like us have gotten by trying to be consistently not stupid, instead of trying to be very intelligent.", author: "Charlie Munger" },
|
|
25
|
+
// Benjamin Graham — The Intelligent Investor, Security Analysis
|
|
26
|
+
{ text: "The investor's chief problem — and even his worst enemy — is likely to be himself.", author: "Benjamin Graham" },
|
|
27
|
+
{ text: "In the short run, the market is a voting machine, but in the long run it is a weighing machine.", author: "Benjamin Graham" },
|
|
28
|
+
// John C. Bogle — founder of Vanguard
|
|
29
|
+
{ text: "Time is your friend; impulse is your enemy.", author: "John C. Bogle" },
|
|
30
|
+
{ text: "The two greatest enemies of the equity fund investor are expenses and emotions.", author: "John C. Bogle" },
|
|
31
|
+
// Peter Lynch — One Up On Wall Street, Beating the Street
|
|
32
|
+
{ text: "Know what you own, and know why you own it.", author: "Peter Lynch" },
|
|
33
|
+
{ text: "The real key to making money in stocks is not to get scared out of them.", author: "Peter Lynch" },
|
|
34
|
+
// Howard Marks — memos and The Most Important Thing
|
|
35
|
+
{ text: "You can't predict. You can prepare.", author: "Howard Marks" },
|
|
36
|
+
// Ray Dalio — Principles
|
|
37
|
+
{ text: "Pain plus reflection equals progress.", author: "Ray Dalio" },
|
|
38
|
+
// Benjamin Franklin — Poor Richard's Almanack and The Way to Wealth
|
|
39
|
+
{ text: "An investment in knowledge pays the best interest.", author: "Benjamin Franklin" },
|
|
40
|
+
{ text: "Beware of little expenses; a small leak will sink a great ship.", author: "Benjamin Franklin" },
|
|
41
|
+
{ text: "If you would be wealthy, think of saving as well as of getting.", author: "Benjamin Franklin" },
|
|
42
|
+
// Andrew Carnegie — The Gospel of Wealth and The Road to Business Success
|
|
43
|
+
{ text: "The man who dies rich dies disgraced.", author: "Andrew Carnegie" },
|
|
44
|
+
{ text: "Concentrate your energies, your thoughts and your capital. The wise man puts all his eggs in one basket and watches the basket.", author: "Andrew Carnegie" },
|
|
45
|
+
// P.T. Barnum — The Art of Money Getting (1880)
|
|
46
|
+
{ text: "Money is a terrible master but an excellent servant.", author: "P.T. Barnum" },
|
|
47
|
+
// Will Rogers — public columns and stage performances
|
|
48
|
+
{ text: "Don't gamble; take all your savings and buy some good stock and hold it till it goes up, then sell it. If it don't go up, don't buy it.", author: "Will Rogers" },
|
|
49
|
+
{ text: "Too many people spend money they haven't earned to buy things they don't want to impress people they don't like.", author: "Will Rogers" },
|
|
50
|
+
// Henry David Thoreau — Walden and Journals
|
|
51
|
+
{ text: "Wealth is the ability to fully experience life.", author: "Henry David Thoreau" },
|
|
52
|
+
{ text: "That man is the richest whose pleasures are the cheapest.", author: "Henry David Thoreau" },
|
|
53
|
+
// Ralph Waldo Emerson — The Conduct of Life (1860)
|
|
54
|
+
{ text: "The first wealth is health.", author: "Ralph Waldo Emerson" },
|
|
55
|
+
// Seneca — Letters from a Stoic
|
|
56
|
+
{ text: "It is not the man who has too little, but the man who craves more, that is poor.", author: "Seneca" },
|
|
57
|
+
{ text: "While we are postponing, life speeds by.", author: "Seneca" },
|
|
58
|
+
// Epictetus — Discourses
|
|
59
|
+
{ text: "Wealth consists not in having great possessions, but in having few wants.", author: "Epictetus" },
|
|
60
|
+
// Confucius — Analects
|
|
61
|
+
{ text: "He who will not economize will have to agonize.", author: "Confucius" },
|
|
62
|
+
// Adam Smith — The Wealth of Nations
|
|
63
|
+
{ text: "Wealth, as Mr. Hobbes says, is power.", author: "Adam Smith" },
|
|
64
|
+
// Ayn Rand — Atlas Shrugged ("Francisco's money speech")
|
|
65
|
+
{ text: "Money is only a tool. It will take you wherever you wish, but it will not replace you as the driver.", author: "Ayn Rand" },
|
|
66
|
+
// Jim Rohn — lectures and books
|
|
67
|
+
{ text: "Discipline is the bridge between goals and accomplishment.", author: "Jim Rohn" },
|
|
68
|
+
{ text: "Formal education will make you a living; self-education will make you a fortune.", author: "Jim Rohn" },
|
|
69
|
+
// Dave Ramsey — books and radio program
|
|
70
|
+
{ text: "A budget is telling your money where to go instead of wondering where it went.", author: "Dave Ramsey" },
|
|
71
|
+
{ text: "If you will live like no one else, later you can live like no one else.", author: "Dave Ramsey" },
|
|
72
|
+
// Suze Orman — books and television
|
|
73
|
+
{ text: "A big part of financial freedom is having your heart and mind free from worry about the what-ifs of life.", author: "Suze Orman" },
|
|
74
|
+
// Robert Kiyosaki — Rich Dad Poor Dad
|
|
75
|
+
{ text: "It's not how much money you make, but how much money you keep, how hard it works for you, and how many generations you keep it for.", author: "Robert Kiyosaki" },
|
|
76
|
+
{ text: "The poor and the middle class work for money. The rich have money work for them.", author: "Robert Kiyosaki" },
|
|
77
|
+
// T. Harv Eker — Secrets of the Millionaire Mind
|
|
78
|
+
{ text: "Your income can grow only to the extent you do.", author: "T. Harv Eker" },
|
|
79
|
+
// Morgan Housel — The Psychology of Money
|
|
80
|
+
{ text: "Spending money to show people how much money you have is the fastest way to have less money.", author: "Morgan Housel" },
|
|
81
|
+
{ text: "Doing well with money has little to do with how smart you are and a lot to do with how you behave.", author: "Morgan Housel" },
|
|
82
|
+
// Naval Ravikant — "How to Get Rich" tweetstorm and podcast
|
|
83
|
+
{ text: "Seek wealth, not money or status. Wealth is having assets that earn while you sleep.", author: "Naval Ravikant" },
|
|
84
|
+
{ text: "You're not going to get rich renting out your time. You must own equity — a piece of a business — to gain your financial freedom.", author: "Naval Ravikant" },
|
|
85
|
+
// George Soros — interviews
|
|
86
|
+
{ text: "It's not whether you're right or wrong that's important, but how much money you make when you're right and how much you lose when you're wrong.", author: "George Soros" },
|
|
87
|
+
];
|
|
88
|
+
export function pickQuote() {
|
|
89
|
+
return QUOTES[Math.floor(Math.random() * QUOTES.length)];
|
|
90
|
+
}
|
package/dist/cli/setup.js
CHANGED
|
@@ -29,7 +29,7 @@ function printBanner() {
|
|
|
29
29
|
}
|
|
30
30
|
function printSummary(dataDir) {
|
|
31
31
|
console.log("");
|
|
32
|
-
console.log(chalk.green("
|
|
32
|
+
console.log(`${chalk.cyan("<°(((><")} ${chalk.green("Plasalid is configured.")}`);
|
|
33
33
|
console.log(chalk.dim(`Config: ${getConfigPath()}`));
|
|
34
34
|
console.log(chalk.dim(`Data: ${dataDir}`));
|
|
35
35
|
console.log("");
|