ei-tui 1.6.2 → 1.6.3
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 +1 -1
- package/src/cli/README.md +2 -0
- package/src/cli/retrieval.ts +22 -0
- package/src/cli.ts +142 -3
- package/src/core/processor.ts +69 -0
- package/src/core/types/entities.ts +1 -0
- package/src/core/utils/message-id.ts +15 -0
- package/src/integrations/claude-code/importer.ts +9 -30
- package/src/integrations/claude-code/types.ts +1 -1
- package/src/integrations/codex/importer.ts +6 -27
- package/src/integrations/codex/types.ts +1 -1
- package/src/integrations/constants.ts +3 -0
- package/src/integrations/cursor/importer.ts +9 -26
- package/src/integrations/cursor/types.ts +1 -1
- package/src/integrations/pi/importer.ts +235 -0
- package/src/integrations/pi/index.ts +3 -0
- package/src/integrations/pi/reader.ts +247 -0
- package/src/integrations/pi/types.ts +151 -0
- package/src/integrations/shared/message-converter.ts +41 -0
- package/tui/README.md +1 -0
- package/tui/src/util/yaml-settings.ts +28 -0
package/package.json
CHANGED
package/src/cli/README.md
CHANGED
|
@@ -36,6 +36,7 @@ ei --id "opencode:jeremys-macbook-pro:ses_38a7...:msg_c75b..."
|
|
|
36
36
|
ei --id "claudecode:my-machine:session-uuid:message-uuid"
|
|
37
37
|
ei --id "cursor:my-machine:composer-uuid:bubble-uuid"
|
|
38
38
|
ei --id "codex:my-machine:thread-uuid:evt_42"
|
|
39
|
+
ei --id "pi:my-machine:session-uuid:session-uuid/entry-id"
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
Quotes surfaced by `ei_search` include a `message_id` field in this format — pipe it to `ei --id` to read the original conversation.
|
|
@@ -56,6 +57,7 @@ This registers Ei with Claude Code, Cursor, Codex, and OpenCode — MCP server c
|
|
|
56
57
|
| **Cursor** | `~/.cursor/mcp.json` | `~/.cursor/hooks.json` (`beforeSubmitPrompt`) + `~/.cursor/hooks/ei-inject.sh` | — |
|
|
57
58
|
| **Codex** | `~/.codex/config.toml` via `codex mcp add ei` | `~/.codex/hooks.json` (`UserPromptSubmit`) + `~/.codex/hooks/ei-inject.ts` | Local Codex agent plugin if installed separately |
|
|
58
59
|
| **OpenCode** | manual (see below) | Via Oh My OpenCode compatibility layer (reads `~/.claude/settings.json`) | `~/.config/opencode/plugins/ei-persona.ts` |
|
|
60
|
+
| **Pi / OMP** | — (tools registered as native Pi extension) | `~/.pi/agent/extensions/ei-integration.ts` (Pi) or `~/.omp/agent/extensions/ei-integration.ts` (OMP) | — |
|
|
59
61
|
|
|
60
62
|
**Context hook**: fires before every message, searches Ei for relevant memory, and injects it silently. No tool call required.
|
|
61
63
|
|
package/src/cli/retrieval.ts
CHANGED
|
@@ -521,6 +521,28 @@ export async function resolveExternalMessage(
|
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
case "pi": {
|
|
525
|
+
if (parsed.machine !== getMachineId()) {
|
|
526
|
+
return { error: `Message is from machine '${parsed.machine}', not available on this machine (${getMachineId()})` };
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const { PiReader } = await import("../integrations/pi/reader.js");
|
|
530
|
+
const reader = new PiReader();
|
|
531
|
+
const win = await reader.getMessageById(parsed.session!, parsed.nativeId, before, after);
|
|
532
|
+
if (!win) return null;
|
|
533
|
+
return {
|
|
534
|
+
type: "opencode_message",
|
|
535
|
+
message: { id: win.message.id, role: win.message.role, content: win.message.content, timestamp: win.message.timestamp },
|
|
536
|
+
before: win.before.map(m => ({ id: m.id, role: m.role, content: m.content, timestamp: m.timestamp })),
|
|
537
|
+
after: win.after.map(m => ({ id: m.id, role: m.role, content: m.content, timestamp: m.timestamp })),
|
|
538
|
+
session: { id: win.session.id, title: win.session.title, directory: win.session.cwd },
|
|
539
|
+
source: "pi",
|
|
540
|
+
};
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
524
546
|
case "unknown":
|
|
525
547
|
default: {
|
|
526
548
|
// Backward compat: bare msg_xxx → treat as opencode (no machine qualifier)
|
package/src/cli.ts
CHANGED
|
@@ -121,6 +121,17 @@ async function installMcpClients(): Promise<void> {
|
|
|
121
121
|
} else {
|
|
122
122
|
console.log(`ℹ️ OpenCode not detected — skipping OpenCode plugin install.`);
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
const hasPi = await Bun.file(join(home, ".pi", "agent", "settings.json")).exists() ||
|
|
126
|
+
await Bun.file(join(home, ".pi", "agent", "auth.json")).exists();
|
|
127
|
+
const hasOmp = await Bun.file(join(home, ".omp", "agent", "settings.json")).exists() ||
|
|
128
|
+
await Bun.file(join(home, ".omp", "agent", "auth.json")).exists();
|
|
129
|
+
|
|
130
|
+
if (hasPi || hasOmp) {
|
|
131
|
+
await installPi();
|
|
132
|
+
} else {
|
|
133
|
+
console.log(`ℹ️ Pi/OMP not detected — skipping Pi extension install.`);
|
|
134
|
+
}
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
async function commandExists(command: string): Promise<boolean> {
|
|
@@ -351,12 +362,11 @@ const heading = \`
|
|
|
351
362
|
*(If you reference anything from it, briefly explain where it came from — e.g. "Ei shows you've been working on X" — so the user isn't confused by knowledge that appeared from nowhere.)*
|
|
352
363
|
|
|
353
364
|
Ei is a personal knowledge base built from the user's coding sessions, Slack, documents, and conversations.
|
|
354
|
-
The following
|
|
365
|
+
The following items MAY be relevant to your current task — use \\\`ei_search\\\` or \\\`ei_lookup\\\` for targeted queries.
|
|
355
366
|
\`;
|
|
356
367
|
|
|
357
368
|
const input = await new Response(Bun.stdin.stream()).json().catch(() => ({}));
|
|
358
369
|
const raw = (input.prompt ?? "").replace(/<[^>]*>/g, "").trim();
|
|
359
|
-
const typeArgs = ["topics", "-n", "5"];
|
|
360
370
|
|
|
361
371
|
const sessionArgs = [];
|
|
362
372
|
if (input.session_id && input.hook_source) {
|
|
@@ -365,7 +375,7 @@ if (input.session_id && input.hook_source) {
|
|
|
365
375
|
sessionArgs.push("--transcript", input.transcript_path);
|
|
366
376
|
}
|
|
367
377
|
|
|
368
|
-
const args = raw ? [
|
|
378
|
+
const args = raw ? ["-n", "5", ...sessionArgs, raw] : ["--recent", "-n", "5"];
|
|
369
379
|
|
|
370
380
|
const output = await $\`bunx ei-tui@latest \${args}\`.quiet().text().catch(() => "");
|
|
371
381
|
if (output.trim()) process.stdout.write(\`\\n\${heading}\\n\${output.trim()}\\n\`);
|
|
@@ -507,6 +517,135 @@ exit 0
|
|
|
507
517
|
console.log(`✓ Installed Ei context hook to ~/.cursor/hooks/ei-inject.sh`);
|
|
508
518
|
}
|
|
509
519
|
|
|
520
|
+
async function installPi(): Promise<void> {
|
|
521
|
+
const home = process.env.HOME || "~";
|
|
522
|
+
const dataPath = process.env.EI_DATA_PATH ?? join(home, ".local", "share", "ei");
|
|
523
|
+
|
|
524
|
+
const extensionContent = `import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
525
|
+
import { Type } from "typebox";
|
|
526
|
+
import { $ } from "bun";
|
|
527
|
+
|
|
528
|
+
export default function eiIntegration(pi: ExtensionAPI) {
|
|
529
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
530
|
+
const entries = ctx.sessionManager.getEntries();
|
|
531
|
+
const recentMsgs = entries
|
|
532
|
+
.filter((e: any) => e.type === "message" && (e.message?.role === "user" || e.message?.role === "assistant"))
|
|
533
|
+
.slice(-5)
|
|
534
|
+
.map((e: any) => {
|
|
535
|
+
const role = e.message?.role ?? "unknown";
|
|
536
|
+
const text = Array.isArray(e.message?.content)
|
|
537
|
+
? e.message.content.filter((b: any) => b.type === "text").map((b: any) => b.text).join(" ")
|
|
538
|
+
: (e.message?.content ?? "");
|
|
539
|
+
return \`\${role}: \${text.slice(0, 200)}\`;
|
|
540
|
+
})
|
|
541
|
+
.join("\\n");
|
|
542
|
+
|
|
543
|
+
const prompt = event.prompt ?? "";
|
|
544
|
+
const args = prompt
|
|
545
|
+
? ["-n", "5", "--", prompt]
|
|
546
|
+
: ["--recent", "-n", "5"];
|
|
547
|
+
|
|
548
|
+
const output = await $\`bunx ei-tui@latest \${args}\`
|
|
549
|
+
.env({ ...process.env, EI_DATA_PATH: "${dataPath}" })
|
|
550
|
+
.quiet()
|
|
551
|
+
.text()
|
|
552
|
+
.catch(() => "");
|
|
553
|
+
|
|
554
|
+
if (!output.trim()) return undefined;
|
|
555
|
+
|
|
556
|
+
const heading = [
|
|
557
|
+
"## Ei Memory Context",
|
|
558
|
+
"*(The user cannot see this block. It is injected automatically before their message.)*",
|
|
559
|
+
"*(If you reference anything from it, briefly explain where it came from.)*",
|
|
560
|
+
"",
|
|
561
|
+
"Ei is a personal knowledge base built from your coding sessions, Slack, documents, and conversations.",
|
|
562
|
+
"The following items MAY be relevant to your current task — use ei_search or ei_lookup for targeted queries.",
|
|
563
|
+
].join("\\n");
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
message: {
|
|
567
|
+
customType: "ei-context",
|
|
568
|
+
content: \`\${heading}\\n\\n\${output.trim()}\`,
|
|
569
|
+
display: false,
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
pi.registerTool({
|
|
575
|
+
name: "ei_search",
|
|
576
|
+
label: "Search Ei Memory",
|
|
577
|
+
description: "Semantic search of Ei's personal knowledge base — facts, topics, people, quotes across all sources. Use when you need context about the user, their work, or anything Ei has learned.",
|
|
578
|
+
promptSnippet: "Search Ei's personal memory for relevant facts, topics, people, or quotes.",
|
|
579
|
+
parameters: Type.Object({
|
|
580
|
+
query: Type.String({ description: "Natural language search query" }),
|
|
581
|
+
type: Type.Optional(Type.Union([
|
|
582
|
+
Type.Literal("facts"),
|
|
583
|
+
Type.Literal("topics"),
|
|
584
|
+
Type.Literal("people"),
|
|
585
|
+
Type.Literal("quotes"),
|
|
586
|
+
Type.Literal("personas"),
|
|
587
|
+
], { description: "Filter to a specific data type. Omit for balanced results across all types." })),
|
|
588
|
+
}),
|
|
589
|
+
async execute(_id, params, _signal, _onUpdate, _ctx) {
|
|
590
|
+
const args = params.type
|
|
591
|
+
? [params.type, "-n", "5", "--", params.query]
|
|
592
|
+
: ["-n", "5", "--", params.query];
|
|
593
|
+
const output = await $\`bunx ei-tui@latest \${args}\`
|
|
594
|
+
.env({ ...process.env, EI_DATA_PATH: "${dataPath}" })
|
|
595
|
+
.quiet()
|
|
596
|
+
.text()
|
|
597
|
+
.catch(() => "No results found");
|
|
598
|
+
return {
|
|
599
|
+
content: [{ type: "text" as const, text: output.trim() || "No results found" }],
|
|
600
|
+
details: {},
|
|
601
|
+
};
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
pi.registerTool({
|
|
606
|
+
name: "ei_lookup",
|
|
607
|
+
label: "Lookup Ei Entity",
|
|
608
|
+
description: "Full-record lookup for a specific Ei entity (Fact, Topic, Person, Quote, or Persona) by ID. Use after ei_search to retrieve complete details for an item.",
|
|
609
|
+
parameters: Type.Object({
|
|
610
|
+
id: Type.String({ description: "Entity ID from ei_search results" }),
|
|
611
|
+
}),
|
|
612
|
+
async execute(_id, params, _signal, _onUpdate, _ctx) {
|
|
613
|
+
const output = await $\`bunx ei-tui@latest --id \${params.id}\`
|
|
614
|
+
.env({ ...process.env, EI_DATA_PATH: "${dataPath}" })
|
|
615
|
+
.quiet()
|
|
616
|
+
.text()
|
|
617
|
+
.catch(() => "Not found");
|
|
618
|
+
return {
|
|
619
|
+
content: [{ type: "text" as const, text: output.trim() || "Not found" }],
|
|
620
|
+
details: {},
|
|
621
|
+
};
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
`;
|
|
626
|
+
|
|
627
|
+
const piExtDir = join(home, ".pi", "agent", "extensions");
|
|
628
|
+
const ompExtDir = join(home, ".omp", "agent", "extensions");
|
|
629
|
+
const extFilename = "ei-integration.ts";
|
|
630
|
+
|
|
631
|
+
const hasPiAgent = await Bun.file(join(home, ".pi", "agent", "auth.json")).exists() ||
|
|
632
|
+
await Bun.file(join(home, ".pi", "agent", "settings.json")).exists();
|
|
633
|
+
const hasOmpAgent = await Bun.file(join(home, ".omp", "agent", "auth.json")).exists() ||
|
|
634
|
+
await Bun.file(join(home, ".omp", "agent", "settings.json")).exists();
|
|
635
|
+
|
|
636
|
+
if (hasPiAgent) {
|
|
637
|
+
await Bun.$`mkdir -p ${piExtDir}`;
|
|
638
|
+
await Bun.write(join(piExtDir, extFilename), extensionContent);
|
|
639
|
+
console.log(`✓ Installed Ei extension to ~/.pi/agent/extensions/${extFilename}`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (hasOmpAgent) {
|
|
643
|
+
await Bun.$`mkdir -p ${ompExtDir}`;
|
|
644
|
+
await Bun.write(join(ompExtDir, extFilename), extensionContent);
|
|
645
|
+
console.log(`✓ Installed Ei extension to ~/.omp/agent/extensions/${extFilename}`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
510
649
|
async function installOpenCodePlugin(): Promise<void> {
|
|
511
650
|
const home = process.env.HOME || "~";
|
|
512
651
|
const opencodeDir = join(home, ".config", "opencode");
|
package/src/core/processor.ts
CHANGED
|
@@ -149,6 +149,7 @@ const DEFAULT_OPENCODE_POLLING_MS = 60000;
|
|
|
149
149
|
const DEFAULT_CLAUDE_CODE_POLLING_MS = 60000;
|
|
150
150
|
const DEFAULT_CURSOR_POLLING_MS = 60000;
|
|
151
151
|
const DEFAULT_CODEX_POLLING_MS = 60000;
|
|
152
|
+
const DEFAULT_PI_POLLING_MS = 60000;
|
|
152
153
|
|
|
153
154
|
let processorInstanceCount = 0;
|
|
154
155
|
|
|
@@ -173,6 +174,8 @@ export class Processor {
|
|
|
173
174
|
private cursorImportInProgress = false;
|
|
174
175
|
private lastCodexSync = 0;
|
|
175
176
|
private codexImportInProgress = false;
|
|
177
|
+
private lastPiSync = 0;
|
|
178
|
+
private piImportInProgress = false;
|
|
176
179
|
private lastSlackSync = 0;
|
|
177
180
|
private slackImportInProgress = false;
|
|
178
181
|
private pendingConflict: StateConflictData | null = null;
|
|
@@ -1296,6 +1299,10 @@ export class Processor {
|
|
|
1296
1299
|
console.log(`[Processor ${this.instanceId}] Clearing codexImportInProgress flag`);
|
|
1297
1300
|
this.codexImportInProgress = false;
|
|
1298
1301
|
}
|
|
1302
|
+
if (this.piImportInProgress) {
|
|
1303
|
+
console.log(`[Processor ${this.instanceId}] Clearing piImportInProgress flag`);
|
|
1304
|
+
this.piImportInProgress = false;
|
|
1305
|
+
}
|
|
1299
1306
|
if (this.slackImportInProgress) {
|
|
1300
1307
|
console.log(`[Processor ${this.instanceId}] Clearing slackImportInProgress flag`);
|
|
1301
1308
|
this.slackImportInProgress = false;
|
|
@@ -1549,6 +1556,14 @@ const toolNextSteps = new Set([
|
|
|
1549
1556
|
await this.checkAndSyncCodex(human, now);
|
|
1550
1557
|
}
|
|
1551
1558
|
|
|
1559
|
+
if (
|
|
1560
|
+
this.isTUI &&
|
|
1561
|
+
human.settings?.pi?.integration &&
|
|
1562
|
+
this.stateManager.queue_length() === 0
|
|
1563
|
+
) {
|
|
1564
|
+
await this.checkAndSyncPi(human, now);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1552
1567
|
if (
|
|
1553
1568
|
this.isTUI &&
|
|
1554
1569
|
human.settings?.personaHistory?.integration &&
|
|
@@ -1860,6 +1875,60 @@ const toolNextSteps = new Set([
|
|
|
1860
1875
|
});
|
|
1861
1876
|
}
|
|
1862
1877
|
|
|
1878
|
+
private async checkAndSyncPi(human: HumanEntity, now: number): Promise<void> {
|
|
1879
|
+
if (this.piImportInProgress) {
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
const pi = human.settings?.pi;
|
|
1884
|
+
const pollingInterval = pi?.polling_interval_ms ?? DEFAULT_PI_POLLING_MS;
|
|
1885
|
+
const lastSync = pi?.last_sync ? new Date(pi.last_sync).getTime() : 0;
|
|
1886
|
+
const timeSinceSync = now - lastSync;
|
|
1887
|
+
|
|
1888
|
+
if (timeSinceSync < pollingInterval && this.lastPiSync > 0) {
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
this.lastPiSync = now;
|
|
1893
|
+
const syncTimestamp = new Date().toISOString();
|
|
1894
|
+
const currentHuman = this.stateManager.getHuman();
|
|
1895
|
+
this.stateManager.setHuman({
|
|
1896
|
+
...currentHuman,
|
|
1897
|
+
settings: {
|
|
1898
|
+
...currentHuman.settings,
|
|
1899
|
+
pi: {
|
|
1900
|
+
...pi,
|
|
1901
|
+
last_sync: syncTimestamp,
|
|
1902
|
+
},
|
|
1903
|
+
},
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
this.piImportInProgress = true;
|
|
1907
|
+
import("../integrations/pi/importer.js")
|
|
1908
|
+
.then(({ importPiSessions }) =>
|
|
1909
|
+
importPiSessions({
|
|
1910
|
+
stateManager: this.stateManager,
|
|
1911
|
+
interface: this.interface,
|
|
1912
|
+
signal: this.importAbortController.signal,
|
|
1913
|
+
})
|
|
1914
|
+
)
|
|
1915
|
+
.then((result) => {
|
|
1916
|
+
if (result.sessionsProcessed > 0) {
|
|
1917
|
+
console.log(
|
|
1918
|
+
`[Processor] Pi sync complete: ${result.sessionsProcessed} sessions, ` +
|
|
1919
|
+
`${result.messagesImported} messages imported, ` +
|
|
1920
|
+
`${result.extractionScansQueued} extraction scans queued`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
})
|
|
1924
|
+
.catch((err) => {
|
|
1925
|
+
console.warn(`[Processor] Pi sync failed:`, err);
|
|
1926
|
+
})
|
|
1927
|
+
.finally(() => {
|
|
1928
|
+
this.piImportInProgress = false;
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1863
1932
|
private async checkAndSyncSlack(human: HumanEntity, now: number): Promise<void> {
|
|
1864
1933
|
if (this.slackImportInProgress) return;
|
|
1865
1934
|
|
|
@@ -131,6 +131,7 @@ export interface HumanSettings {
|
|
|
131
131
|
claudeCode?: import("../../integrations/claude-code/types.js").ClaudeCodeSettings;
|
|
132
132
|
cursor?: import("../../integrations/cursor/types.js").CursorSettings;
|
|
133
133
|
codex?: import("../../integrations/codex/types.js").CodexSettings;
|
|
134
|
+
pi?: import("../../integrations/pi/types.js").PiSettings;
|
|
134
135
|
document?: DocumentSettings;
|
|
135
136
|
active_theme?: string;
|
|
136
137
|
custom_themes?: ThemeDefinition[];
|
|
@@ -15,6 +15,7 @@ export type MessageIdIntegration =
|
|
|
15
15
|
| "claudecode"
|
|
16
16
|
| "cursor"
|
|
17
17
|
| "codex"
|
|
18
|
+
| "pi"
|
|
18
19
|
| "import"
|
|
19
20
|
| "slack"
|
|
20
21
|
| "unknown"
|
|
@@ -79,6 +80,16 @@ export function parseMessageId(id: string): ParsedMessageId {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
if (parts[0] === "pi" && parts.length >= 4) {
|
|
84
|
+
return {
|
|
85
|
+
integration: "pi",
|
|
86
|
+
machine: parts[1],
|
|
87
|
+
session: parts[2],
|
|
88
|
+
nativeId: parts.slice(3).join(":"),
|
|
89
|
+
raw: id,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
82
93
|
if (parts[0] === "import" && parts[1] === "document" && parts.length >= 4) {
|
|
83
94
|
return {
|
|
84
95
|
integration: "import",
|
|
@@ -125,6 +136,10 @@ export function qualifyCodexMessage(machine: string, sessionId: string, nativeId
|
|
|
125
136
|
return `codex:${machine}:${sessionId}:${nativeId}`
|
|
126
137
|
}
|
|
127
138
|
|
|
139
|
+
export function qualifyPiMessage(machine: string, sessionId: string, nativeId: string): string {
|
|
140
|
+
return `pi:${machine}:${sessionId}:${nativeId}`
|
|
141
|
+
}
|
|
142
|
+
|
|
128
143
|
export function qualifyDocumentMessage(slug: string, uuid: string): string {
|
|
129
144
|
return `import:document:${slug}:${uuid}`
|
|
130
145
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { StateManager } from "../../core/state-manager.js";
|
|
2
|
-
import type { Ei_Interface, Message,
|
|
2
|
+
import type { Ei_Interface, Message, PersonaEntity, PersonaTrait } from "../../core/types.js";
|
|
3
3
|
import { DEFAULT_SEED_TRAITS } from "../../core/constants/seed-traits.js";
|
|
4
|
-
import type { IClaudeCodeReader, ClaudeCodeSession
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
MIN_SESSION_AGE_MS,
|
|
8
|
-
} from "./types.js";
|
|
4
|
+
import type { IClaudeCodeReader, ClaudeCodeSession } from "./types.js";
|
|
5
|
+
import { CLAUDE_CODE_PERSONA_NAME } from "./types.js";
|
|
6
|
+
import { MIN_SESSION_AGE_MS } from "../constants.js";
|
|
9
7
|
import { ClaudeCodeReader } from "./reader.js";
|
|
10
8
|
import {
|
|
11
9
|
queueAllScans,
|
|
@@ -16,8 +14,10 @@ import {
|
|
|
16
14
|
queueTopicRewritePhase,
|
|
17
15
|
} from "../../core/orchestrators/ceremony.js";
|
|
18
16
|
import { isProcessRunning } from "../process-check.js";
|
|
19
|
-
import { getMachineId } from "../machine-id.js";
|
|
20
17
|
import { qualifyClaudeCodeMessage } from "../../core/utils/message-id.js";
|
|
18
|
+
import { getMachineId } from "../machine-id.js";
|
|
19
|
+
import { convertToEiMessage, convertToPreMarkedEiMessage } from "../shared/message-converter.js";
|
|
20
|
+
import { TWELVE_HOURS_MS } from "../constants.js";
|
|
21
21
|
|
|
22
22
|
// =============================================================================
|
|
23
23
|
// Export Types
|
|
@@ -41,30 +41,9 @@ export interface ClaudeCodeImporterOptions {
|
|
|
41
41
|
// Utility Functions
|
|
42
42
|
// =============================================================================
|
|
43
43
|
|
|
44
|
-
const TWELVE_HOURS_MS = 43_200_000;
|
|
45
44
|
const CLAUDE_CODE_GROUP = "Claude Code";
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
id: qualifyClaudeCodeMessage(getMachineId(), sessionId, msg.id),
|
|
50
|
-
role: msg.role === "user" ? "human" : "system",
|
|
51
|
-
content: msg.content,
|
|
52
|
-
timestamp: msg.timestamp,
|
|
53
|
-
read: true,
|
|
54
|
-
context_status: "default" as ContextStatus,
|
|
55
|
-
external: true,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function convertToPreMarkedEiMessage(msg: ClaudeCodeMessage, sessionId: string): Message {
|
|
60
|
-
return {
|
|
61
|
-
...convertToEiMessage(msg, sessionId),
|
|
62
|
-
f: true,
|
|
63
|
-
t: true,
|
|
64
|
-
p: true,
|
|
65
|
-
e: true,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
46
|
+
const qualify = qualifyClaudeCodeMessage;
|
|
68
47
|
|
|
69
48
|
/**
|
|
70
49
|
* Ensure the single "Claude Code" persona exists.
|
|
@@ -249,7 +228,7 @@ export async function importClaudeCodeSessions(
|
|
|
249
228
|
for (const msg of messages) {
|
|
250
229
|
const msgMs = new Date(msg.timestamp).getTime();
|
|
251
230
|
const isOld = cutoffMs !== null && msgMs < cutoffMs;
|
|
252
|
-
const eiMsg = isOld ? convertToPreMarkedEiMessage(msg, targetSession.id) : convertToEiMessage(msg, targetSession.id);
|
|
231
|
+
const eiMsg = isOld ? convertToPreMarkedEiMessage(msg, targetSession.id, qualify) : convertToEiMessage(msg, targetSession.id, qualify);
|
|
253
232
|
stateManager.messages_append(persona.id, eiMsg);
|
|
254
233
|
result.messagesImported++;
|
|
255
234
|
if (!isOld) toAnalyze.push(eiMsg);
|
|
@@ -139,7 +139,7 @@ export const CLAUDE_CODE_TOPIC_GROUPS = ["General", "Coding", "Claude Code"];
|
|
|
139
139
|
* Minimum session age before we import it.
|
|
140
140
|
* Mirrors OpenCode's 20-minute rule — gives the session time to "settle."
|
|
141
141
|
*/
|
|
142
|
-
export
|
|
142
|
+
export { MIN_SESSION_AGE_MS } from "../constants.js";
|
|
143
143
|
|
|
144
144
|
// ============================================================================
|
|
145
145
|
// Human Settings Shape (mirrors OpenCodeSettings in core/types.ts)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { StateManager } from "../../core/state-manager.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Ei_Interface, Message, PersonaEntity, PersonaTrait } from "../../core/types.js";
|
|
3
3
|
import { DEFAULT_SEED_TRAITS } from "../../core/constants/seed-traits.js";
|
|
4
4
|
import {
|
|
5
5
|
queueAllScans,
|
|
@@ -10,16 +10,16 @@ import {
|
|
|
10
10
|
queueTopicRewritePhase,
|
|
11
11
|
} from "../../core/orchestrators/ceremony.js";
|
|
12
12
|
import { qualifyCodexMessage } from "../../core/utils/message-id.js";
|
|
13
|
+
import { convertToEiMessage, convertToPreMarkedEiMessage } from "../shared/message-converter.js";
|
|
13
14
|
import { getMachineId } from "../machine-id.js";
|
|
14
15
|
import { isProcessRunning } from "../process-check.js";
|
|
15
16
|
import { CodexReader } from "./reader.js";
|
|
16
17
|
import {
|
|
17
18
|
CODEX_PERSONA_NAME,
|
|
18
|
-
MIN_SESSION_AGE_MS,
|
|
19
|
-
type CodexMessage,
|
|
20
19
|
type CodexSession,
|
|
21
20
|
type ICodexReader,
|
|
22
21
|
} from "./types.js";
|
|
22
|
+
import { MIN_SESSION_AGE_MS, TWELVE_HOURS_MS } from "../constants.js";
|
|
23
23
|
|
|
24
24
|
export interface CodexImportResult {
|
|
25
25
|
sessionsProcessed: number;
|
|
@@ -35,30 +35,9 @@ export interface CodexImporterOptions {
|
|
|
35
35
|
signal?: AbortSignal;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const TWELVE_HOURS_MS = 43_200_000;
|
|
39
38
|
const CODEX_GROUP = "Codex";
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
id: qualifyCodexMessage(getMachineId(), sessionId, msg.id),
|
|
44
|
-
role: msg.role === "user" ? "human" : "system",
|
|
45
|
-
content: msg.content,
|
|
46
|
-
timestamp: msg.timestamp,
|
|
47
|
-
read: true,
|
|
48
|
-
context_status: "default" as ContextStatus,
|
|
49
|
-
external: true,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function convertToPreMarkedEiMessage(msg: CodexMessage, sessionId: string): Message {
|
|
54
|
-
return {
|
|
55
|
-
...convertToEiMessage(msg, sessionId),
|
|
56
|
-
f: true,
|
|
57
|
-
t: true,
|
|
58
|
-
p: true,
|
|
59
|
-
e: true,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
40
|
+
const qualify = qualifyCodexMessage;
|
|
62
41
|
|
|
63
42
|
function ensureCodexPersona(
|
|
64
43
|
stateManager: StateManager,
|
|
@@ -212,8 +191,8 @@ export async function importCodexSessions(
|
|
|
212
191
|
const msgMs = new Date(msg.timestamp).getTime();
|
|
213
192
|
const isOld = cutoffMs !== null && msgMs < cutoffMs;
|
|
214
193
|
const eiMsg = isOld
|
|
215
|
-
? convertToPreMarkedEiMessage(msg, targetSession.id)
|
|
216
|
-
: convertToEiMessage(msg, targetSession.id);
|
|
194
|
+
? convertToPreMarkedEiMessage(msg, targetSession.id, qualify)
|
|
195
|
+
: convertToEiMessage(msg, targetSession.id, qualify);
|
|
217
196
|
|
|
218
197
|
stateManager.messages_append(persona.id, eiMsg);
|
|
219
198
|
result.messagesImported++;
|
|
@@ -94,7 +94,7 @@ export const CODEX_TOPIC_GROUPS = ["General", "Coding", "Codex"];
|
|
|
94
94
|
* Minimum session age before import.
|
|
95
95
|
* Mirrors Claude Code / Cursor's 20-minute rule so active sessions can settle.
|
|
96
96
|
*/
|
|
97
|
-
export
|
|
97
|
+
export { MIN_SESSION_AGE_MS } from "../constants.js";
|
|
98
98
|
|
|
99
99
|
// ============================================================================
|
|
100
100
|
// Human Settings Shape
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import type { StateManager } from "../../core/state-manager.js";
|
|
2
|
-
import type { Ei_Interface, Message,
|
|
2
|
+
import type { Ei_Interface, Message, PersonaEntity, PersonaTrait } from "../../core/types.js";
|
|
3
3
|
import { DEFAULT_SEED_TRAITS } from "../../core/constants/seed-traits.js";
|
|
4
4
|
import type { ICursorReader, CursorSession, CursorMessage } from "./types.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
MIN_SESSION_AGE_MS,
|
|
8
|
-
} from "./types.js";
|
|
5
|
+
import { CURSOR_PERSONA_NAME } from "./types.js";
|
|
6
|
+
import { MIN_SESSION_AGE_MS, TWELVE_HOURS_MS } from "../constants.js";
|
|
9
7
|
import { CursorReader } from "./reader.js";
|
|
10
8
|
import { isProcessRunning } from "../process-check.js";
|
|
11
9
|
import { getMachineId } from "../machine-id.js";
|
|
12
10
|
import { qualifyCursorMessage } from "../../core/utils/message-id.js";
|
|
11
|
+
import { convertToEiMessage, convertToPreMarkedEiMessage } from "../shared/message-converter.js";
|
|
13
12
|
import {
|
|
14
13
|
queueAllScans,
|
|
15
14
|
type ExtractionContext,
|
|
@@ -33,29 +32,12 @@ export interface CursorImporterOptions {
|
|
|
33
32
|
signal?: AbortSignal;
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
const TWELVE_HOURS_MS = 43_200_000;
|
|
37
35
|
const CURSOR_GROUP = "Cursor";
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
id: qualifyCursorMessage(getMachineId(), sessionId, msg.id),
|
|
42
|
-
role: msg.type === 1 ? "human" : "system",
|
|
43
|
-
content: msg.text,
|
|
44
|
-
timestamp: msg.timestamp,
|
|
45
|
-
read: true,
|
|
46
|
-
context_status: "default" as ContextStatus,
|
|
47
|
-
external: true,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
37
|
+
const qualify = qualifyCursorMessage;
|
|
50
38
|
|
|
51
|
-
function
|
|
52
|
-
return {
|
|
53
|
-
...convertToEiMessage(msg, sessionId),
|
|
54
|
-
f: true,
|
|
55
|
-
t: true,
|
|
56
|
-
p: true,
|
|
57
|
-
e: true,
|
|
58
|
-
};
|
|
39
|
+
function normalizeCursorMessage(msg: CursorMessage) {
|
|
40
|
+
return { id: msg.id, role: (msg.type === 1 ? "user" : "assistant") as "user" | "assistant", content: msg.text, timestamp: msg.timestamp };
|
|
59
41
|
}
|
|
60
42
|
|
|
61
43
|
function ensureCursorPersona(
|
|
@@ -209,7 +191,8 @@ export async function importCursorSessions(
|
|
|
209
191
|
for (const msg of messages) {
|
|
210
192
|
const msgMs = new Date(msg.timestamp).getTime();
|
|
211
193
|
const isOld = cutoffMs !== null && msgMs < cutoffMs;
|
|
212
|
-
const
|
|
194
|
+
const normalized = normalizeCursorMessage(msg);
|
|
195
|
+
const eiMsg = isOld ? convertToPreMarkedEiMessage(normalized, targetSession.id, qualify) : convertToEiMessage(normalized, targetSession.id, qualify);
|
|
213
196
|
stateManager.messages_append(persona.id, eiMsg);
|
|
214
197
|
result.messagesImported++;
|
|
215
198
|
if (!isOld) toAnalyze.push(eiMsg);
|
|
@@ -116,7 +116,7 @@ export const CURSOR_TOPIC_GROUPS = ["General", "Coding", "Cursor"];
|
|
|
116
116
|
* Minimum session age before we import it.
|
|
117
117
|
* Mirrors ClaudeCode's 20-minute rule — gives the session time to "settle."
|
|
118
118
|
*/
|
|
119
|
-
export
|
|
119
|
+
export { MIN_SESSION_AGE_MS } from "../constants.js";
|
|
120
120
|
|
|
121
121
|
// ============================================================================
|
|
122
122
|
// Human Settings Shape
|