create-evda 0.1.7 → 0.1.8
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/CHANGELOG.md +9 -0
- package/README.md +5 -2
- package/bin/evda.mjs +61 -3
- package/create-evda.mjs +16 -5
- package/dist/pairing.mjs +4246 -0
- package/lib/circle-plan.mjs +24 -1
- package/lib/mcp.mjs +48 -4
- package/lib/pairing.mjs +165 -0
- package/lib/setup.mjs +41 -0
- package/package.json +4 -2
- package/skills/evda/SKILL.md +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ Plain-English notes on how Iris behaves differently after each version. Not a co
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
Your phone can join your EVÐA. `evda pair` on the computer your record is on shows a one-time code
|
|
8
|
+
and a QR link; it works once, for 10 minutes, and the phone gets only the parts you name (the
|
|
9
|
+
journal by default). `evda sync` adds the phone once it has used the code, takes in what it wrote,
|
|
10
|
+
and sends what changed; a note written on the phone lands under the day, in your words. `evda
|
|
11
|
+
devices` lists what is paired and `evda remove <device>` removes one: it adds nothing more and
|
|
12
|
+
receives nothing new. You can also ask your AI to pair a phone (`evda_pair_device`). Pairing goes
|
|
13
|
+
through a relay that cannot read what it carries, and needs Node 22.13 or newer; everything else
|
|
14
|
+
still runs on Node 20.
|
|
15
|
+
|
|
7
16
|
Organizing is on from the start. Setup installs the morning organize on a Mac (a launchd job that
|
|
8
17
|
runs your own Claude each morning at 8:00, or when the computer wakes), says so in one line, and
|
|
9
18
|
offers off: answer no during setup, pass `--no-routines`, or later run `evda routine morning
|
package/README.md
CHANGED
|
@@ -91,9 +91,12 @@ apply places only what is new, in the record's own folders.
|
|
|
91
91
|
evda_write_skill writes or revises a skill at .claude/skills/<name>/SKILL.md. Never outside that folder, never a rule about your life
|
|
92
92
|
evda_create_subrecord an EVÐA inside this one, with the four roles and its own AGENTS.md, then doctor. Your evda.json changes only on bounded with confirmed: true
|
|
93
93
|
evda_doctor evda doctor, as a report
|
|
94
|
-
|
|
94
|
+
evda_pair_device a one-time code and QR link for your phone, and what to do on it; the phone gets only the parts named
|
|
95
|
+
evda_sync_devices adds a phone that used its code, takes in what your devices wrote, sends what changed
|
|
96
|
+
evda_devices the devices paired with your EVÐA and what each reads
|
|
97
|
+
evda_check_update whether newer EVDA tooling exists
|
|
95
98
|
|
|
96
|
-
Every write appends, refuses anything shaped like a password, key or token, and says the path it wrote.
|
|
99
|
+
Every write appends, refuses anything shaped like a password, key or token, and says the path it wrote. Two kinds of call leave this machine. `evda_check_update` asks the npm registry for a version number; it carries nothing from the record and installs nothing, and the update itself is a command you run in a terminal. The device tools talk to the relay that carries your EVÐA between your own devices, and everything they send is sealed here first: the relay cannot read it. `evda connect` prints the setup for your record, with its path filled in:
|
|
97
100
|
|
|
98
101
|
```bash
|
|
99
102
|
claude mcp add --scope user evda -- /path/to/node /path/to/evda.mjs mcp ~/my-evda # Claude Code
|
package/bin/evda.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import { holderOf, HOLDER_KINDS } from "../lib/manifest.mjs";
|
|
|
26
26
|
import { EvdaError, isEntry } from "../lib/errors.mjs";
|
|
27
27
|
import { publishCommand } from "../lib/publish.mjs";
|
|
28
28
|
import { checkForUpdate, summarize, stampInstalledVersion } from "../lib/update-check.mjs";
|
|
29
|
-
import { planCircles, coverage, declare, deviceCircles, planBeside } from "../lib/circle-plan.mjs";
|
|
29
|
+
import { planCircles, coverage, declare, deviceCircles, planBeside, defaultParts } from "../lib/circle-plan.mjs";
|
|
30
30
|
import { circleIndex, grantView } from "../lib/filechanges.mjs";
|
|
31
31
|
import { mountRoot } from "../lib/declarations.mjs";
|
|
32
32
|
import { excluder } from "../lib/sync-rules.mjs";
|
|
@@ -35,6 +35,7 @@ import { note, releaseNote, ownerSearch, readContext, readersFor, describeReader
|
|
|
35
35
|
import { runHook } from "../lib/guard.mjs";
|
|
36
36
|
import { markdownFiles } from "../lib/circles.mjs";
|
|
37
37
|
import { refuseSecrets, message as messageTool, replies as repliesTool } from "../lib/tools.mjs";
|
|
38
|
+
import * as pairing from "../lib/pairing.mjs";
|
|
38
39
|
|
|
39
40
|
const HELP = `evda: operate an EVDA. The files are the truth; this is optional.
|
|
40
41
|
|
|
@@ -112,6 +113,14 @@ const HELP = `evda: operate an EVDA. The files are the truth; this is optional.
|
|
|
112
113
|
add the preset's folders and manifests. Only adds: an existing
|
|
113
114
|
manifest that differs is a CONFLICT and is left as it is
|
|
114
115
|
evda preset diff <id> [dir] what the preset has that this record does not
|
|
116
|
+
evda pair [dir] [--name phone] [--parts journal[,people]] [--json]
|
|
117
|
+
a one-time code (and a QR link) for a phone to join this EVÐA. It
|
|
118
|
+
works once, for 10 minutes, and gives the device only the parts named
|
|
119
|
+
evda devices [dir] [--json] the devices paired with this EVÐA, and what each reads
|
|
120
|
+
evda remove <device> [dir] remove a device: it adds nothing more and receives nothing new
|
|
121
|
+
evda sync [dir] add devices that used their code, take in what they sent, send what
|
|
122
|
+
changed. Pairing needs Node 22.13 or newer; --relay <url> for another relay
|
|
123
|
+
--json on pair, devices, remove and sync prints data for an app, no ids
|
|
115
124
|
evda status [dir] one screen summary
|
|
116
125
|
evda publish public [dir] --out <dir> --key <seed file>
|
|
117
126
|
the public circle as a signed plaintext export; refuses (exit 2) on a
|
|
@@ -123,6 +132,10 @@ const HELP = `evda: operate an EVDA. The files are the truth; this is optional.
|
|
|
123
132
|
circles and grants roles and their folders; moves and changes no file.
|
|
124
133
|
A circle rooted at "" is written as the root itself, and a circle
|
|
125
134
|
already declared keeps its id, release, rules, mount and policy
|
|
135
|
+
evda circles default [--dry-run] [--json] [dir]
|
|
136
|
+
split a record that has no parts into the default two: journal (the
|
|
137
|
+
day files and captures) and private (everything else). A record that
|
|
138
|
+
has parts is left as it is. --dry-run says what it would make
|
|
126
139
|
evda circles check [--plan <file>] [dir]
|
|
127
140
|
every syncable file in exactly one circle (CHECKED n files); fails on a
|
|
128
141
|
file no circle covers, and on a symlink, iCloud stub or oversize file.
|
|
@@ -172,7 +185,7 @@ function isDirArg(a) {
|
|
|
172
185
|
}
|
|
173
186
|
|
|
174
187
|
// `--name value` or `--name=value`. The value of a `--name value` pair is not a positional.
|
|
175
|
-
const VALUE_FLAGS = ["to", "kind", "name", "role", "by", "as", "model", "project", "hour", "weekday", "out", "key", "plan", "device", "shadow", "record-id", "env-file", "port", "host", "public-url", "log", "lifeline", "ttl"];
|
|
188
|
+
const VALUE_FLAGS = ["to", "kind", "name", "role", "by", "as", "model", "project", "hour", "weekday", "out", "key", "plan", "device", "shadow", "record-id", "env-file", "port", "host", "public-url", "log", "lifeline", "ttl", "parts", "relay"];
|
|
176
189
|
function flagValue(args, name) {
|
|
177
190
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
178
191
|
if (eq) return eq.slice(name.length + 3);
|
|
@@ -492,6 +505,41 @@ async function main(argv) {
|
|
|
492
505
|
return 0;
|
|
493
506
|
}
|
|
494
507
|
|
|
508
|
+
case "pair": {
|
|
509
|
+
const { dir } = pickDir(args);
|
|
510
|
+
const r = await pairing.pair(dir, { name: flagValue(args, "name"), parts: flagValue(args, "parts"), relay: flagValue(args, "relay") });
|
|
511
|
+
// --json is for an app (the EVÐA Mac app, HQ-1925): the code and its QR link, and nothing with an id.
|
|
512
|
+
if (args.includes("--json")) console.log(JSON.stringify({ name: r.name, code: r.code, qr: r.qr, computer: r.computer, expiresAt: r.expiresAt, parts: r.parts }));
|
|
513
|
+
else for (const line of r.lines) console.log(line);
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
case "devices":
|
|
518
|
+
case "sync": {
|
|
519
|
+
const { dir } = pickDir(args);
|
|
520
|
+
const relay = flagValue(args, "relay");
|
|
521
|
+
if (args.includes("--json")) {
|
|
522
|
+
console.log(JSON.stringify(cmd === "devices" ? await pairing.deviceData(dir, { relay }) : await pairing.syncData(dir, { relay })));
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
const lines = await pairing[cmd](dir, { relay });
|
|
526
|
+
for (const line of lines) console.log(line);
|
|
527
|
+
return 0;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
case "remove": {
|
|
531
|
+
// The device's name comes first and is never read as the record, even when a folder by
|
|
532
|
+
// that name sits in the working directory.
|
|
533
|
+
const values = new Set(VALUE_FLAGS.map((n) => args.indexOf(`--${n}`)).filter((i) => i >= 0).map((i) => i + 1));
|
|
534
|
+
const at = args.findIndex((a, i) => !a.startsWith("--") && !values.has(i));
|
|
535
|
+
const name = at >= 0 ? args[at] : flagValue(args, "name");
|
|
536
|
+
const { dir } = pickDir(at >= 0 ? args.filter((_, i) => i !== at) : args);
|
|
537
|
+
const lines = await pairing.remove(dir, name, { relay: flagValue(args, "relay") });
|
|
538
|
+
if (args.includes("--json")) console.log(JSON.stringify({ removed: String(name).trim(), lines }));
|
|
539
|
+
else for (const line of lines) console.log(line);
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
495
543
|
case "doctor": {
|
|
496
544
|
const { dir, via } = pickDir(args);
|
|
497
545
|
return doctor(dir, { via }).exit;
|
|
@@ -839,6 +887,16 @@ async function main(argv) {
|
|
|
839
887
|
console.log(`wrote ${written.length} files: ${written.map((p) => relative(dir, p)).join(", ")}`);
|
|
840
888
|
return 0;
|
|
841
889
|
}
|
|
890
|
+
if (sub === "default") {
|
|
891
|
+
// The default parts (HQ-1925), for a record that has none: the app's one button, on the
|
|
892
|
+
// owner's yes. --dry-run is the question and writes nothing.
|
|
893
|
+
const r = defaultParts(dir, { dryRun: args.includes("--dry-run") });
|
|
894
|
+
if (args.includes("--json")) { console.log(JSON.stringify(r)); return 0; }
|
|
895
|
+
if (r.declared) console.log(`Split into parts: ${r.parts.join(", ")}. Nothing in your files moved.`);
|
|
896
|
+
else if (r.needed) console.log(`Not split into parts yet. Saying yes makes: ${r.parts.join(", ")}. Nothing was written.`);
|
|
897
|
+
else console.log(`This EVÐA is already split into parts: ${r.parts.join(", ")}. Nothing was written.`);
|
|
898
|
+
return 0;
|
|
899
|
+
}
|
|
842
900
|
if (sub === "check") {
|
|
843
901
|
const evda = openEvda(dir);
|
|
844
902
|
// Without --plan, the plan kept beside the circles (`<circles role>/plan.json`) says what is
|
|
@@ -855,7 +913,7 @@ async function main(argv) {
|
|
|
855
913
|
if (r.checked === 0) { console.log("FAIL CHECKED 0: nothing was examined"); return 1; }
|
|
856
914
|
return r.problems.length ? 1 : 0;
|
|
857
915
|
}
|
|
858
|
-
throw new EvdaError("circles needs plan, declare or check", { exit: 2 });
|
|
916
|
+
throw new EvdaError("circles needs plan, declare, default or check", { exit: 2 });
|
|
859
917
|
}
|
|
860
918
|
|
|
861
919
|
case "parity": {
|
package/create-evda.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// create-evda <dir> [--agent-name NAME] [--owner NAME] [--purpose TEXT] [--voice TEXT]
|
|
5
5
|
// [--platform claude|chatgpt|gemini|cursor|other] [--training-off] [--yes]
|
|
6
|
-
// [--filing everything|asked] [--no-prompt-hook]
|
|
6
|
+
// [--filing everything|asked] [--no-prompt-hook] [--no-parts]
|
|
7
7
|
// [--name NAME] [--layers record,ledger[,circles]] [--preset ID|none] [--force]
|
|
8
8
|
//
|
|
9
9
|
// On a terminal, any setup answer not given as a flag is asked, one question at a time, and setup
|
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Writes the manifest, the operating layer with the owner's answers in its owner section, the
|
|
15
15
|
// loaders for the tools that do not read AGENTS.md on their own, the corpus, the ledger, and
|
|
16
|
-
// .evda/.
|
|
16
|
+
// .evda/. Splits the record into its default parts (HQ-1925): journal, the day files and captures,
|
|
17
|
+
// and private, everything else, both held by this computer, so a phone can be added with no
|
|
18
|
+
// terminal step (--no-parts leaves it unsplit, for parts declared by hand). Then runs `evda doctor`
|
|
19
|
+
// on what it wrote. It creates no example notes: an empty record
|
|
17
20
|
// that fills with a real life is the product. The one file it writes under record/ is today's
|
|
18
21
|
// daily file holding a true event, the record's own creation, so the first session has something
|
|
19
22
|
// dated to read and the owner sees where their words go.
|
|
@@ -35,7 +38,8 @@ import { installHook } from "./lib/hooks.mjs";
|
|
|
35
38
|
import { applyPreset, listPresets, planLines, presetIds, NONE } from "./lib/presets.mjs";
|
|
36
39
|
import { FILING_MODES } from "./lib/day-file.mjs";
|
|
37
40
|
import { installPromptHook } from "./lib/hooks.mjs";
|
|
38
|
-
import {
|
|
41
|
+
import { defaultParts } from "./lib/circle-plan.mjs";
|
|
42
|
+
import { PLATFORMS, PLATFORM_KEYS, PARTS_LINE, LOADERS, DESKTOP_FILESYSTEM, DICTATION, DEFAULT_AGENT_NAME, platformKey, fillOwnerSection, aiLine, interview, trainingSection, termsSection, readerFor, INTRO } from "./lib/setup.mjs";
|
|
39
43
|
import { describeReaders, parseReaders } from "./lib/readers.mjs";
|
|
40
44
|
|
|
41
45
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
@@ -65,13 +69,13 @@ const FOLDER_TYPES = {
|
|
|
65
69
|
messages: "message",
|
|
66
70
|
};
|
|
67
71
|
|
|
68
|
-
const USAGE = `usage: create-evda <dir> [--agent-name NAME] [--owner NAME] [--purpose TEXT] [--voice TEXT] [--platform ${PLATFORM_KEYS.join("|")}] [--training-off] [--filing everything|asked] [--no-prompt-hook] [--yes] [--name NAME] [--kind person|agent|company] [--id ATLAS-ID] [--layers record,ledger] [--skill] [--routines morning,weekly] [--preset ${presetIds().join("|")}|none] [--use] [--no-pointer] [--no-guard]`;
|
|
72
|
+
const USAGE = `usage: create-evda <dir> [--agent-name NAME] [--owner NAME] [--purpose TEXT] [--voice TEXT] [--platform ${PLATFORM_KEYS.join("|")}] [--training-off] [--filing everything|asked] [--no-prompt-hook] [--no-parts] [--yes] [--name NAME] [--kind person|agent|company] [--id ATLAS-ID] [--layers record,ledger] [--skill] [--routines morning,weekly] [--preset ${presetIds().join("|")}|none] [--use] [--no-pointer] [--no-guard]`;
|
|
69
73
|
|
|
70
74
|
export function parseArgs(argv) {
|
|
71
75
|
const opts = {
|
|
72
76
|
layers: ["record", "ledger"], force: false, name: null, owner: "", kind: "person", id: null, pointer: true, use: false,
|
|
73
77
|
yes: false, trainingOff: false, agentName: null, purpose: "", voice: "", platform: null, platformName: null,
|
|
74
|
-
skill: null, routines: null, preset: null, guard: true, filing: null, promptHook: true,
|
|
78
|
+
skill: null, routines: null, preset: null, guard: true, filing: null, promptHook: true, parts: true,
|
|
75
79
|
};
|
|
76
80
|
const args = argv.flatMap((a) => {
|
|
77
81
|
const eq = /^(--[a-z-]+)=([\s\S]*)$/.exec(a);
|
|
@@ -92,6 +96,7 @@ export function parseArgs(argv) {
|
|
|
92
96
|
else if (a === "--skill") opts.skill = true;
|
|
93
97
|
else if (a === "--no-guard") opts.guard = false;
|
|
94
98
|
else if (a === "--no-prompt-hook") opts.promptHook = false;
|
|
99
|
+
else if (a === "--no-parts") opts.parts = false;
|
|
95
100
|
else if (a === "--filing") {
|
|
96
101
|
opts.filing = value(++i, a).trim().toLowerCase();
|
|
97
102
|
if (!FILING_MODES.includes(opts.filing)) throw new EvdaError(`--filing takes ${FILING_MODES.join(" or ")}, got ${args[i]}`, { exit: 2 });
|
|
@@ -302,6 +307,12 @@ export async function main(argv, io = null) {
|
|
|
302
307
|
for (const l of planLines(plan)) log(l);
|
|
303
308
|
log("");
|
|
304
309
|
}
|
|
310
|
+
// The default parts, after the preset so its folders exist, and before doctor so doctor reads them.
|
|
311
|
+
if (opts.parts !== false) {
|
|
312
|
+
const parts = defaultParts(dir);
|
|
313
|
+
log(parts.parts.includes("journal") ? PARTS_LINE : `parts: ${parts.parts.join(", ")}`);
|
|
314
|
+
log("");
|
|
315
|
+
}
|
|
305
316
|
const report = doctor(dir, { out: log, via: "create-evda" });
|
|
306
317
|
if (report.exit !== 0) {
|
|
307
318
|
console.error("\ncreate-evda wrote the layout but doctor does not pass on it. That is a bug in create-evda; the output above says where.");
|