ei-tui 1.6.4 → 1.6.6
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/package.json +2 -2
- package/src/cli/README.md +13 -0
- package/src/cli.ts +81 -0
- package/tui/src/storage/file.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ei-tui",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.6",
|
|
4
4
|
"author": "Flare576",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"@opentui/core": "^0.1.79",
|
|
64
64
|
"@opentui/solid": "^0.1.79",
|
|
65
65
|
"fastembed": "^2.1.0",
|
|
66
|
-
"solid-js": "1.9.9",
|
|
66
|
+
"solid-js": "^1.9.9",
|
|
67
67
|
"yaml": "^2.8.2"
|
|
68
68
|
}
|
|
69
69
|
}
|
package/src/cli/README.md
CHANGED
|
@@ -16,6 +16,7 @@ ei --persona "Beta" --recent # Most recently mentioned items Beta has
|
|
|
16
16
|
ei --id <id> # Look up entity by ID — or fetch a message by FQ ID
|
|
17
17
|
echo <id> | ei --id # Look up entity by ID from stdin
|
|
18
18
|
ei --install # Wire Ei into Claude Code, Cursor, Codex, and OpenCode (MCP + hooks + persona plugin)
|
|
19
|
+
ei --sync # Pull latest state from remote sync server into state.backup.json (no TUI required)
|
|
19
20
|
ei mcp # Start the Ei MCP stdio server (for Claude Code/Cursor/Codex)
|
|
20
21
|
```
|
|
21
22
|
|
|
@@ -133,3 +134,15 @@ All search commands return arrays. Each result includes a `type` field.
|
|
|
133
134
|
**Persona**: `{ type, id, display_name, short_description, model, base_prompt, traits[], topics[] }`
|
|
134
135
|
|
|
135
136
|
**ID lookup** (`lookup: true`): single object (not an array) with the same shape.
|
|
137
|
+
|
|
138
|
+
## Quick Sync
|
|
139
|
+
|
|
140
|
+
Sometimes you want your latest Ei profile available on a machine without running the full TUI — especially if the TUI is already running on another machine.
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
ei --sync
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Pulls the latest state from your remote sync server and saves it to `state.backup.json`. Works the same credential hierarchy as the TUI: reads from `state.backup.json` first, falls back to `EI_SYNC_USERNAME` / `EI_SYNC_PASSPHRASE` environment variables if not present.
|
|
147
|
+
|
|
148
|
+
Will abort (with a clear error) if `state.json` already exists on this machine — that means Ei has run here and a conflict resolution step would be needed on next launch.
|
package/src/cli.ts
CHANGED
|
@@ -70,6 +70,7 @@ Options:
|
|
|
70
70
|
--source, -s Filter to entities from a specific source (prefix match, e.g. "cursor", "codex:my-machine", "opencode:my-machine:ses_abc123")
|
|
71
71
|
--id Look up entity by ID (accepts value or stdin)
|
|
72
72
|
--install Register Ei with Claude Code, Cursor, Codex, and OpenCode (MCP + context hooks where supported)
|
|
73
|
+
--sync Pull latest state from remote sync server into state.backup.json (no TUI required)
|
|
73
74
|
--session <id> Session ID to enrich the query with recent context (use with --hook-source)
|
|
74
75
|
--hook-source <src> Source of the hook: "opencode-plugin" (OpenCode SQLite), "cursor", or "codex"
|
|
75
76
|
--transcript <path> Path to a Claude Code JSONL transcript file for context enrichment
|
|
@@ -145,6 +146,86 @@ async function main(): Promise<void> {
|
|
|
145
146
|
process.exit(0);
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
if (args[0] === "--sync") {
|
|
150
|
+
const { getDataPath } = await import("./cli/retrieval.js");
|
|
151
|
+
const { RemoteSync } = await import("./storage/remote.js");
|
|
152
|
+
const { decodeAllEmbeddings, encodeAllEmbeddings } = await import("./storage/embeddings.js");
|
|
153
|
+
const { join } = await import("path");
|
|
154
|
+
const { readFile, writeFile, rename, mkdir } = await import("fs/promises");
|
|
155
|
+
|
|
156
|
+
const dataPath = getDataPath();
|
|
157
|
+
const statePath = join(dataPath, "state.json");
|
|
158
|
+
const lockPath = join(dataPath, "ei.lock");
|
|
159
|
+
const backupPath = join(dataPath, "state.backup.json");
|
|
160
|
+
|
|
161
|
+
// Fail if state.json exists — implies Ei ran here and a conflict would arise on next start
|
|
162
|
+
try {
|
|
163
|
+
await readFile(statePath);
|
|
164
|
+
process.stderr.write(
|
|
165
|
+
`\nei --sync aborted: state.json already exists at ${dataPath}\n\n` +
|
|
166
|
+
`This machine has local Ei data. Running --sync here would create a conflict\n` +
|
|
167
|
+
`the next time you start Ei.\n\n` +
|
|
168
|
+
`If you want to pull from remote anyway, delete state.json first.\n\n`
|
|
169
|
+
);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
} catch { /* file doesn't exist — good */ }
|
|
172
|
+
|
|
173
|
+
// Fail if ei.lock exists with a live process — Ei is actively running
|
|
174
|
+
try {
|
|
175
|
+
const lockText = await readFile(lockPath, "utf-8");
|
|
176
|
+
const lock = JSON.parse(lockText) as { pid: number; started: string };
|
|
177
|
+
try {
|
|
178
|
+
process.kill(lock.pid, 0);
|
|
179
|
+
process.stderr.write(
|
|
180
|
+
`\nei --sync aborted: Ei is already running on this machine.\n` +
|
|
181
|
+
` PID: ${lock.pid}\n` +
|
|
182
|
+
` Started: ${lock.started}\n\n` +
|
|
183
|
+
`Stop Ei before syncing.\n\n`
|
|
184
|
+
);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
} catch { /* PID is dead — stale lock, proceed */ }
|
|
187
|
+
} catch { /* no lock file — good */ }
|
|
188
|
+
|
|
189
|
+
// Resolve sync credentials: prefer stored backup state, fall back to env vars
|
|
190
|
+
let username: string | undefined;
|
|
191
|
+
let passphrase: string | undefined;
|
|
192
|
+
try {
|
|
193
|
+
const backupText = await readFile(backupPath, "utf-8");
|
|
194
|
+
const backup = decodeAllEmbeddings(JSON.parse(backupText));
|
|
195
|
+
username = backup?.human?.settings?.sync?.username;
|
|
196
|
+
passphrase = backup?.human?.settings?.sync?.passphrase;
|
|
197
|
+
} catch { /* no backup — fall through to env vars */ }
|
|
198
|
+
|
|
199
|
+
username ??= process.env.EI_SYNC_USERNAME;
|
|
200
|
+
passphrase ??= process.env.EI_SYNC_PASSPHRASE;
|
|
201
|
+
|
|
202
|
+
if (!username || !passphrase) {
|
|
203
|
+
process.stderr.write(
|
|
204
|
+
`\nei --sync aborted: no sync credentials found.\n\n` +
|
|
205
|
+
`Set EI_SYNC_USERNAME and EI_SYNC_PASSPHRASE environment variables,\n` +
|
|
206
|
+
`or run Ei normally first to store credentials in state.backup.json.\n\n`
|
|
207
|
+
);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const remote = new RemoteSync();
|
|
212
|
+
await remote.configure({ username, passphrase });
|
|
213
|
+
|
|
214
|
+
const result = await remote.fetch();
|
|
215
|
+
if (!result.success || !result.state) {
|
|
216
|
+
process.stderr.write(`\nei --sync failed: ${result.error ?? "unknown error"}\n\n`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await mkdir(dataPath, { recursive: true });
|
|
221
|
+
const tempPath = `${backupPath}.tmp.${Date.now()}`;
|
|
222
|
+
await writeFile(tempPath, JSON.stringify(encodeAllEmbeddings(result.state), null, 2), "utf-8");
|
|
223
|
+
await rename(tempPath, backupPath);
|
|
224
|
+
|
|
225
|
+
process.stdout.write(`\nei --sync complete. Remote state saved to state.backup.json.\n\n`);
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
148
229
|
// Handle --id flag: look up entity by ID
|
|
149
230
|
const idFlagIndex = args.indexOf("--id");
|
|
150
231
|
if (idFlagIndex !== -1) {
|
package/tui/src/storage/file.ts
CHANGED
|
@@ -86,6 +86,12 @@ export class FileStorage implements Storage {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
async saveBackup(state: StorageState): Promise<void> {
|
|
90
|
+
await this.ensureDataDir();
|
|
91
|
+
const backupPath = join(this.dataPath, BACKUP_FILE);
|
|
92
|
+
await this.atomicWrite(backupPath, JSON.stringify(encodeAllEmbeddings(state), null, 2));
|
|
93
|
+
}
|
|
94
|
+
|
|
89
95
|
/**
|
|
90
96
|
* Read backup state without removing it.
|
|
91
97
|
* Used to peek sync credentials from a previous session's backup.
|