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 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
- evda_check_update whether newer EVDA tooling exists. The only tool that reaches the network
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. The one call that leaves this machine is `evda_check_update` asking 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. `evda connect` prints the setup for your record, with its path filled in:
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/. Then runs `evda doctor` on what it wrote. It creates no example notes: an empty record
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 { PLATFORMS, PLATFORM_KEYS, LOADERS, DESKTOP_FILESYSTEM, DICTATION, DEFAULT_AGENT_NAME, platformKey, fillOwnerSection, aiLine, interview, trainingSection, termsSection, readerFor, INTRO } from "./lib/setup.mjs";
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.");