create-evda 0.1.0 → 0.1.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 +44 -14
- package/bin/evda.mjs +3 -1
- package/create-evda.mjs +114 -29
- package/lib/agents.mjs +1 -1
- package/lib/doctor.mjs +52 -1
- package/lib/kinds.mjs +301 -0
- package/lib/kinds.schema.json +684 -0
- package/lib/setup.mjs +204 -0
- package/package.json +2 -2
- package/templates/AGENTS.md +273 -140
- package/templates/CLAUDE.md +1 -1
- package/templates/GEMINI.md +3 -0
- package/templates/README.md +29 -0
- package/templates/evda-digest.md +71 -32
package/lib/setup.mjs
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Setup: the questions a new owner answers once, the platforms their EVDA works with, and where
|
|
2
|
+
// model training is turned off on each. Used by create-evda interactively (one question at a
|
|
3
|
+
// time on a terminal) and unattended (flags, or --yes). The answers land in the owner section of
|
|
4
|
+
// AGENTS.md, "What this EVÐA is", in the owner's words.
|
|
5
|
+
//
|
|
6
|
+
// The training instructions were checked against each provider's own help page on 2026-09-16.
|
|
7
|
+
// A provider renames a toggle every so often, so each entry carries the link to its page and
|
|
8
|
+
// says the setting's name as that page gives it.
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_AGENT_NAME = "Iris";
|
|
11
|
+
|
|
12
|
+
export const PLATFORMS = {
|
|
13
|
+
claude: {
|
|
14
|
+
label: "Claude (the Claude apps, or Claude Code)",
|
|
15
|
+
training: [
|
|
16
|
+
"In Claude, open Settings, then Privacy, and turn off \"Help improve our AI models\".",
|
|
17
|
+
"The same setting covers Claude Code when you sign in with a Free, Pro or Max plan. Work and API accounts are not trained on by default.",
|
|
18
|
+
],
|
|
19
|
+
url: "https://privacy.claude.com/en/articles/12109829-how-do-i-change-my-model-improvement-privacy-settings",
|
|
20
|
+
open: ["Claude Code: run `claude` in this folder. It reads CLAUDE.md, which loads AGENTS.md."],
|
|
21
|
+
},
|
|
22
|
+
chatgpt: {
|
|
23
|
+
label: "ChatGPT or Codex",
|
|
24
|
+
training: [
|
|
25
|
+
"In ChatGPT, open Settings, then Data Controls, and turn off \"Improve the model for everyone\".",
|
|
26
|
+
"On a personal plan the same setting covers Codex. Business and API accounts are not trained on by default.",
|
|
27
|
+
],
|
|
28
|
+
url: "https://help.openai.com/en/articles/7730893-data-controls-faq",
|
|
29
|
+
open: ["Codex: run `codex` in this folder. It reads AGENTS.md on its own."],
|
|
30
|
+
},
|
|
31
|
+
gemini: {
|
|
32
|
+
label: "Gemini (the Gemini app, or Gemini CLI)",
|
|
33
|
+
training: [
|
|
34
|
+
"In the Gemini app, open Settings & help, then Activity, and turn Keep Activity off.",
|
|
35
|
+
"In Gemini CLI signed in with a personal Google account, run /privacy and decline data use, or use a paid Gemini API key, which Google does not train on.",
|
|
36
|
+
],
|
|
37
|
+
url: "https://support.google.com/gemini/answer/13278892",
|
|
38
|
+
open: ["Gemini CLI: run `gemini` in this folder. It reads GEMINI.md, which loads AGENTS.md."],
|
|
39
|
+
},
|
|
40
|
+
cursor: {
|
|
41
|
+
label: "Cursor",
|
|
42
|
+
training: [
|
|
43
|
+
"In Cursor, open Cursor Settings (Cmd+Shift+J on a Mac, Ctrl+Shift+J elsewhere), then General, and turn Privacy Mode on.",
|
|
44
|
+
],
|
|
45
|
+
url: "https://cursor.com/help/security-and-privacy/privacy",
|
|
46
|
+
open: ["Cursor: open this folder. It reads AGENTS.md on its own."],
|
|
47
|
+
},
|
|
48
|
+
other: {
|
|
49
|
+
label: "Something else",
|
|
50
|
+
training: [
|
|
51
|
+
"Open your AI's privacy or data settings and turn off anything that lets it use your conversations to train or improve its models.",
|
|
52
|
+
"If you cannot find one, check its terms say your data is not used for training before you write anything personal.",
|
|
53
|
+
],
|
|
54
|
+
url: null,
|
|
55
|
+
open: ["Any agent that reads AGENTS.md (Codex, Cursor, GitHub Copilot, Windsurf, Zed, opencode and others) works in this folder as it is. Aider: `aider --read AGENTS.md`."],
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const PLATFORM_KEYS = Object.keys(PLATFORMS);
|
|
60
|
+
|
|
61
|
+
const SHORT_NAMES = { claude: "Claude", chatgpt: "ChatGPT", gemini: "Gemini", cursor: "Cursor", other: "" };
|
|
62
|
+
|
|
63
|
+
// The "Turning training off" section of the scaffolded README, generated from PLATFORMS so the
|
|
64
|
+
// owner's copy and the setup flow say the same thing.
|
|
65
|
+
export function trainingSection() {
|
|
66
|
+
return PLATFORM_KEYS.map((k) => {
|
|
67
|
+
const p = PLATFORMS[k];
|
|
68
|
+
return `- **${p.label}.** ${p.training.join(" ")}${p.url ? ` <${p.url}>` : ""}`;
|
|
69
|
+
}).join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// A chat app with project instructions cannot read or write the folder. It can still be used.
|
|
73
|
+
export const CHAT_APPS = [
|
|
74
|
+
"ChatGPT or Claude in the browser or app: make a Project, paste AGENTS.md into its instructions, and add your record files to it. Anything you write there stays in that app until you copy it into record/.",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// The loaders: one line that imports AGENTS.md, for the tools that do not read it on their own.
|
|
78
|
+
// Codex, Cursor, Copilot, Windsurf, Zed and the others on agents.md read AGENTS.md directly and
|
|
79
|
+
// need nothing.
|
|
80
|
+
export const LOADERS = {
|
|
81
|
+
"CLAUDE.md": "Claude Code",
|
|
82
|
+
"GEMINI.md": "Gemini CLI",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const DICTATION = "Talking is often easier than typing. Apple's built-in dictation works well, or Wispr Flow with data collection turned off.";
|
|
86
|
+
|
|
87
|
+
// Accepts a key, a number from the menu, or a word from the label ("codex" -> chatgpt).
|
|
88
|
+
export function platformKey(answer) {
|
|
89
|
+
const a = String(answer ?? "").trim().toLowerCase();
|
|
90
|
+
if (!a) return null;
|
|
91
|
+
if (/^\d+$/.test(a)) return PLATFORM_KEYS[Number(a) - 1] ?? null;
|
|
92
|
+
if (PLATFORMS[a]) return a;
|
|
93
|
+
if (/codex|openai|gpt/.test(a)) return "chatgpt";
|
|
94
|
+
if (/claude|anthropic/.test(a)) return "claude";
|
|
95
|
+
if (/gemini|google/.test(a)) return "gemini";
|
|
96
|
+
if (/cursor/.test(a)) return "cursor";
|
|
97
|
+
return "other";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The owner's answers as the owner section's lines. A field left empty stays empty, so Iris asks
|
|
101
|
+
// for it in the first session.
|
|
102
|
+
export const FIELDS = [
|
|
103
|
+
["agentName", "Agent name"],
|
|
104
|
+
["owner", "Who I am"],
|
|
105
|
+
["purpose", "What I want this for"],
|
|
106
|
+
["voice", "How I want it to talk to me"],
|
|
107
|
+
["ai", "The AI I use with it, and its training setting"],
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
function oneLine(s) {
|
|
111
|
+
return String(s ?? "").replace(/\s*\n\s*/g, " ").trim();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The value written on the AI line. Empty unless training off was confirmed, because an AI with
|
|
115
|
+
// training in an unknown state is the thing setup exists to prevent, and an empty line is what
|
|
116
|
+
// makes Iris ask.
|
|
117
|
+
export function aiLine({ platform, platformName, trainingOff, date }) {
|
|
118
|
+
if (!trainingOff) return "";
|
|
119
|
+
const name = oneLine(platformName) || (platform ? SHORT_NAMES[platform] : "") || "";
|
|
120
|
+
return `${name ? `${name}. ` : ""}Training off, confirmed ${date}.`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fill the owner section of an AGENTS.md text. Each field's line is `- **Label:** value`; a line
|
|
124
|
+
// present in the template is filled in place, a missing one is added at the end of the section's
|
|
125
|
+
// list. Only the section "## What this EVÐA is" is touched.
|
|
126
|
+
export function fillOwnerSection(text, answers) {
|
|
127
|
+
const lines = text.split("\n");
|
|
128
|
+
const start = lines.findIndex((l) => /^## What this EVÐA is\s*$/.test(l));
|
|
129
|
+
if (start === -1) return text;
|
|
130
|
+
let end = lines.findIndex((l, i) => i > start && (/^## /.test(l) || l.startsWith("<!-- evda:generated")));
|
|
131
|
+
if (end === -1) end = lines.length;
|
|
132
|
+
let lastField = -1;
|
|
133
|
+
for (const [key, label] of FIELDS) {
|
|
134
|
+
const value = oneLine(answers[key]);
|
|
135
|
+
const re = new RegExp(`^- \\*\\*${label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\*\\*`);
|
|
136
|
+
const i = lines.findIndex((l, j) => j > start && j < end && re.test(l));
|
|
137
|
+
const line = `- **${label}:**${value ? ` ${value}` : ""}`;
|
|
138
|
+
if (i !== -1) {
|
|
139
|
+
if (value || key !== "agentName") lines[i] = line;
|
|
140
|
+
lastField = Math.max(lastField, i);
|
|
141
|
+
} else if (value) {
|
|
142
|
+
const at = lastField !== -1 ? lastField + 1 : end;
|
|
143
|
+
lines.splice(at, 0, line);
|
|
144
|
+
lastField = at;
|
|
145
|
+
end += 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return lines.join("\n");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const WELCOME = [
|
|
152
|
+
"EVÐA is a really organized journal that helps you live your life the more you use it.",
|
|
153
|
+
"",
|
|
154
|
+
"Freewrite with Iris about anything: what stresses you out, what you are unsure of, the things you do every day that feel slightly off. You will get better at seeing your thoughts, understanding why you do what you do, why certain people matter to you and why you react the way you do. Iris cannot give you the right answer for your life, but it is good at suggestions: take them, push back on what you disagree with, get specific, and get a new response back. Iris keeps it all in your EVÐA as you talk.",
|
|
155
|
+
"",
|
|
156
|
+
"It is not easy to put your deepest thoughts into a system, especially ones you have not admitted to yourself, never mind a friend or a therapist. EVÐA is built for exactly that, and it is open source so you can check. Your data is always yours, and never accessible to anyone at EVÐA: the company never receives your record. The AI you use does receive what Iris reads, which is why setup asks you to turn training off first.",
|
|
157
|
+
"",
|
|
158
|
+
"The only requirement is that you want to, and you are open to it. Iris is the default name; call it whatever you like.",
|
|
159
|
+
].join("\n");
|
|
160
|
+
|
|
161
|
+
// Ask the setup questions one at a time. `io.ask(prompt)` returns the answer (a promise or a
|
|
162
|
+
// string); `io.out(line)` prints. Only questions whose flag is missing are asked. Returns the
|
|
163
|
+
// answers, including `trainingOff` which is true only after the owner typed yes.
|
|
164
|
+
export async function interview(opts, io) {
|
|
165
|
+
const out = io.out;
|
|
166
|
+
const ask = async (q) => oneLine(await io.ask(q));
|
|
167
|
+
out(WELCOME);
|
|
168
|
+
out("");
|
|
169
|
+
const a = { ...opts };
|
|
170
|
+
if (a.agentName == null) a.agentName = (await ask(`What would you like to call your agent? (${DEFAULT_AGENT_NAME}) `)) || DEFAULT_AGENT_NAME;
|
|
171
|
+
const agent = a.agentName || DEFAULT_AGENT_NAME;
|
|
172
|
+
if (!a.owner) a.owner = await ask("What is your name? ");
|
|
173
|
+
if (!a.purpose) a.purpose = await ask(`What do you want ${agent} for? Say it however it comes out. `);
|
|
174
|
+
if (!a.voice) a.voice = await ask(`How do you want ${agent} to talk to you? `);
|
|
175
|
+
if (!a.platform) {
|
|
176
|
+
out("Which AI will you use it with?");
|
|
177
|
+
PLATFORM_KEYS.forEach((k, i) => out(` ${i + 1}. ${PLATFORMS[k].label}`));
|
|
178
|
+
let key = null;
|
|
179
|
+
let raw = "";
|
|
180
|
+
while (!key) {
|
|
181
|
+
raw = await ask("Number or name: ");
|
|
182
|
+
key = platformKey(raw);
|
|
183
|
+
}
|
|
184
|
+
a.platform = key;
|
|
185
|
+
if (key === "other" && !/^\d+$/.test(raw) && raw.toLowerCase() !== "other") a.platformName = raw;
|
|
186
|
+
}
|
|
187
|
+
const p = PLATFORMS[a.platform];
|
|
188
|
+
if (!a.trainingOff) {
|
|
189
|
+
out("");
|
|
190
|
+
out("Before anything personal goes in, turn model training off:");
|
|
191
|
+
for (const line of p.training) out(` ${line}`);
|
|
192
|
+
if (p.url) out(` ${p.url}`);
|
|
193
|
+
let yes = false;
|
|
194
|
+
while (!yes) {
|
|
195
|
+
const r = (await ask("Type yes once training is off: ")).toLowerCase();
|
|
196
|
+
yes = r === "yes" || r === "y";
|
|
197
|
+
if (!yes) out("Setup finishes once training is off. Take your time; nothing has been written yet.");
|
|
198
|
+
}
|
|
199
|
+
a.trainingOff = true;
|
|
200
|
+
}
|
|
201
|
+
out("");
|
|
202
|
+
out(DICTATION);
|
|
203
|
+
return a;
|
|
204
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-evda",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Make your own EVÐA: a
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Make your own EVÐA: a running journal of your life with Iris, as plain files on your machine, for any AI. No runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-evda": "create-evda.mjs",
|