ei-tui 0.6.4 → 0.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 +1 -1
- package/src/core/handlers/dedup.ts +4 -1
- package/src/core/handlers/human-matching.ts +14 -7
- package/src/core/handlers/index.ts +1 -0
- package/src/core/llm-client.ts +12 -1
- package/src/core/orchestrators/human-extraction.ts +70 -11
- package/src/core/orchestrators/index.ts +2 -0
- package/src/core/processor.ts +3 -0
- package/src/core/queue-processor.ts +8 -5
- package/src/core/state-manager.ts +18 -0
- package/src/core/types/entities.ts +3 -1
- package/src/core/types/enums.ts +1 -0
- package/src/prompts/ceremony/dedup.ts +89 -1
- package/src/prompts/ceremony/index.ts +2 -1
- package/src/prompts/ceremony/types.ts +8 -0
- package/tui/src/commands/editor.tsx +3 -0
- package/tui/src/commands/help.tsx +1 -1
- package/tui/src/commands/me.tsx +66 -23
- package/tui/src/components/HelpOverlay.tsx +63 -33
- package/tui/src/context/ei.tsx +9 -2
- package/tui/src/context/overlay.tsx +2 -0
- package/tui/src/index.tsx +7 -0
- package/tui/src/util/e2e-flags.ts +13 -0
- package/tui/src/util/editor.ts +36 -3
- package/tui/src/util/help-content.ts +136 -0
- package/tui/src/util/yaml-human.ts +162 -40
- package/tui/src/util/yaml-persona.ts +11 -10
- package/tui/src/util/yaml-provider.ts +34 -9
- package/tui/src/util/yaml-settings.ts +21 -15
package/tui/src/commands/me.tsx
CHANGED
|
@@ -6,37 +6,73 @@ import { ConfirmOverlay } from "../components/ConfirmOverlay.js";
|
|
|
6
6
|
|
|
7
7
|
type DataType = "facts" | "topics" | "people";
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const TYPE_ALIASES: Record<string, DataType> = {
|
|
10
|
+
facts: "facts", fact: "facts",
|
|
11
|
+
topics: "topics", topic: "topics",
|
|
12
|
+
people: "people", person: "people", persons: "people",
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
export const meCommand: Command = {
|
|
12
16
|
name: "me",
|
|
13
17
|
aliases: [],
|
|
14
18
|
description: "Edit your data in $EDITOR",
|
|
15
|
-
usage: "/me [
|
|
16
|
-
|
|
19
|
+
usage: "/me [fact|topic|person] [new | <search>]",
|
|
20
|
+
|
|
17
21
|
async execute(args, ctx) {
|
|
18
22
|
const human = await ctx.ei.getHuman();
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const filterType: DataType | null =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (filterArg && !filterType) {
|
|
26
|
-
ctx.showNotification(`Invalid type: ${filterArg}. Use: facts, topics, people`, "error");
|
|
23
|
+
|
|
24
|
+
const typeArg = args[0]?.toLowerCase();
|
|
25
|
+
const filterType: DataType | null = typeArg ? (TYPE_ALIASES[typeArg] ?? null) : null;
|
|
26
|
+
|
|
27
|
+
if (typeArg && !filterType) {
|
|
28
|
+
ctx.showNotification(`Unknown type: ${typeArg}. Use: fact, topic, person`, "error");
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
|
|
32
|
+
const secondArg = args[1]?.toLowerCase();
|
|
33
|
+
const isNew = secondArg === "new";
|
|
34
|
+
const searchTerm = !isNew && secondArg ? args.slice(1).join(" ") : null;
|
|
35
|
+
|
|
36
|
+
if (isNew && args.length > 2) {
|
|
37
|
+
ctx.showNotification(
|
|
38
|
+
`Use /me ${typeArg} new to create, or /me ${typeArg} ${args.slice(2).join(" ")} to search`,
|
|
39
|
+
"error"
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if ((isNew || searchTerm) && !filterType) {
|
|
45
|
+
ctx.showNotification(`Specify a type first: /me fact|topic|person [new | <search>]`, "error");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const filterItems = <T extends { name: string }>(items: T[]): T[] => {
|
|
50
|
+
if (isNew) return [];
|
|
51
|
+
if (searchTerm) return items.filter(i => i.name.toLowerCase().includes(searchTerm.toLowerCase()));
|
|
52
|
+
return items;
|
|
53
|
+
};
|
|
54
|
+
|
|
30
55
|
const filteredHuman = filterType ? {
|
|
31
56
|
...human,
|
|
32
|
-
facts:
|
|
33
|
-
topics: filterType === "topics" ? human.topics : [],
|
|
34
|
-
people: filterType === "people" ? human.people : [],
|
|
57
|
+
facts: filterType === "facts" ? filterItems(human.facts) : [],
|
|
58
|
+
topics: filterType === "topics" ? filterItems(human.topics) : [],
|
|
59
|
+
people: filterType === "people" ? filterItems(human.people) : [],
|
|
35
60
|
} : human;
|
|
61
|
+
|
|
62
|
+
const isEmpty = filteredHuman.facts.length === 0
|
|
63
|
+
&& filteredHuman.topics.length === 0
|
|
64
|
+
&& filteredHuman.people.length === 0;
|
|
65
|
+
|
|
66
|
+
if (searchTerm && isEmpty) {
|
|
67
|
+
ctx.showNotification(`No ${filterType} matching "${searchTerm}" — open editor to create one`, "info");
|
|
68
|
+
}
|
|
36
69
|
|
|
37
70
|
const personaLookup = new Map(ctx.ei.personas().map(p => [p.id, p.display_name]));
|
|
38
71
|
const allGroups = await ctx.ei.getGroupList();
|
|
39
|
-
|
|
72
|
+
const sections = filterType
|
|
73
|
+
? new Set<"facts" | "topics" | "people">([filterType])
|
|
74
|
+
: new Set<"facts" | "topics" | "people">(["facts", "topics", "people"]);
|
|
75
|
+
let yamlContent = humanToYAML(filteredHuman, personaLookup, allGroups, sections);
|
|
40
76
|
let editorIteration = 0;
|
|
41
77
|
|
|
42
78
|
while (true) {
|
|
@@ -67,7 +103,8 @@ export const meCommand: Command = {
|
|
|
67
103
|
}
|
|
68
104
|
|
|
69
105
|
try {
|
|
70
|
-
const
|
|
106
|
+
const currentHuman = await ctx.ei.getHuman();
|
|
107
|
+
const parsed = humanFromYAML(result.content, filteredHuman, currentHuman);
|
|
71
108
|
|
|
72
109
|
for (const id of parsed.deletedFactIds) {
|
|
73
110
|
await ctx.ei.removeDataItem("fact", id);
|
|
@@ -95,14 +132,20 @@ export const meCommand: Command = {
|
|
|
95
132
|
}
|
|
96
133
|
}
|
|
97
134
|
|
|
98
|
-
const deleteCount = parsed.deletedFactIds.length +
|
|
99
|
-
parsed.deletedTopicIds.length +
|
|
135
|
+
const deleteCount = parsed.deletedFactIds.length +
|
|
136
|
+
parsed.deletedTopicIds.length +
|
|
100
137
|
parsed.deletedPersonIds.length;
|
|
101
|
-
const updateCount = parsed.changedFactIds.size +
|
|
102
|
-
parsed.changedTopicIds.size +
|
|
138
|
+
const updateCount = parsed.changedFactIds.size +
|
|
139
|
+
parsed.changedTopicIds.size +
|
|
103
140
|
parsed.changedPersonIds.size;
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
const skippedCount = parsed.skippedFactCount +
|
|
142
|
+
parsed.skippedTopicCount +
|
|
143
|
+
parsed.skippedPersonCount;
|
|
144
|
+
|
|
145
|
+
const msg = skippedCount > 0
|
|
146
|
+
? `Updated ${updateCount}, deleted ${deleteCount}, skipped ${skippedCount} (changed by another process)`
|
|
147
|
+
: `Updated ${updateCount} items, deleted ${deleteCount}`;
|
|
148
|
+
ctx.showNotification(msg, "info");
|
|
106
149
|
return;
|
|
107
150
|
|
|
108
151
|
} catch (parseError) {
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { useKeyboard } from "@opentui/solid";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { onMount, onCleanup } from "solid-js";
|
|
3
|
+
import type { CliRenderer } from "@opentui/core";
|
|
4
4
|
import { useKeyboardNav } from "../context/keyboard.js";
|
|
5
|
+
import { spawnPager } from "../util/editor.js";
|
|
6
|
+
import { buildManPage } from "../util/help-content.js";
|
|
5
7
|
|
|
6
8
|
interface HelpOverlayProps {
|
|
7
9
|
onDismiss: () => void;
|
|
10
|
+
renderer: CliRenderer;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export function HelpOverlay(props: HelpOverlayProps) {
|
|
@@ -14,11 +17,14 @@ export function HelpOverlay(props: HelpOverlayProps) {
|
|
|
14
17
|
|
|
15
18
|
useKeyboard((event) => {
|
|
16
19
|
event.preventDefault();
|
|
17
|
-
|
|
20
|
+
if (event.name === "m") {
|
|
21
|
+
props.onDismiss();
|
|
22
|
+
void spawnPager(buildManPage(), props.renderer);
|
|
23
|
+
} else {
|
|
24
|
+
props.onDismiss();
|
|
25
|
+
}
|
|
18
26
|
});
|
|
19
27
|
|
|
20
|
-
const commands = getAllCommands();
|
|
21
|
-
|
|
22
28
|
return (
|
|
23
29
|
<box
|
|
24
30
|
position="absolute"
|
|
@@ -31,43 +37,67 @@ export function HelpOverlay(props: HelpOverlayProps) {
|
|
|
31
37
|
justifyContent="center"
|
|
32
38
|
>
|
|
33
39
|
<box
|
|
34
|
-
width={
|
|
40
|
+
width={82}
|
|
35
41
|
backgroundColor="#1a1a2e"
|
|
36
42
|
borderStyle="single"
|
|
37
43
|
borderColor="#586e75"
|
|
38
44
|
padding={2}
|
|
39
45
|
flexDirection="column"
|
|
46
|
+
gap={1}
|
|
40
47
|
>
|
|
41
48
|
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
<box flexDirection="row" gap={2}>
|
|
50
|
+
|
|
51
|
+
<box flexDirection="column" gap={1} width={38}>
|
|
52
|
+
<box flexDirection="column">
|
|
53
|
+
<text fg="#eee8d5">Keybindings</text>
|
|
54
|
+
<text fg="#93a1a1"> Ctrl+E Open $EDITOR (preserves input)</text>
|
|
55
|
+
<text fg="#93a1a1"> Ctrl+C Clear input / exit</text>
|
|
56
|
+
<text fg="#93a1a1"> Ctrl+B Toggle sidebar</text>
|
|
57
|
+
<text fg="#93a1a1"> Escape Abort / resume queue</text>
|
|
58
|
+
<text fg="#93a1a1"> PgUp/Dn Scroll messages</text>
|
|
59
|
+
</box>
|
|
60
|
+
|
|
61
|
+
<box flexDirection="column">
|
|
62
|
+
<text fg="#eee8d5">Core</text>
|
|
63
|
+
<text fg="#93a1a1"> /set Edit global settings</text>
|
|
64
|
+
<text fg="#93a1a1"> /q /q! Quit (! = skip sync)</text>
|
|
65
|
+
<text fg="#93a1a1"> /provider Manage LLM providers</text>
|
|
66
|
+
<text fg="#93a1a1"> /me Edit your data</text>
|
|
67
|
+
<text fg="#93a1a1"> /d /d <name> Edit persona details</text>
|
|
68
|
+
</box>
|
|
69
|
+
</box>
|
|
70
|
+
|
|
71
|
+
<box flexDirection="column" gap={1} width={38}>
|
|
72
|
+
<box flexDirection="column">
|
|
73
|
+
<text fg="#eee8d5">Persona</text>
|
|
74
|
+
<text fg="#93a1a1"> /p /p new /p update</text>
|
|
75
|
+
<text fg="#93a1a1"> /context Message context</text>
|
|
76
|
+
<text fg="#93a1a1"> /pause /resume</text>
|
|
77
|
+
</box>
|
|
78
|
+
|
|
79
|
+
<box flexDirection="column">
|
|
80
|
+
<text fg="#eee8d5">Rooms</text>
|
|
81
|
+
<text fg="#93a1a1"> /r /r new Room picker / create</text>
|
|
82
|
+
<text fg="#93a1a1"> /activate Advance active node</text>
|
|
83
|
+
<text fg="#93a1a1"> /silence Pass your turn</text>
|
|
84
|
+
</box>
|
|
85
|
+
|
|
86
|
+
<box flexDirection="column">
|
|
87
|
+
<text fg="#eee8d5">Extended</text>
|
|
88
|
+
<text fg="#93a1a1"> /tools Tool providers</text>
|
|
89
|
+
<text fg="#93a1a1"> /auth spotify Spotify OAuth</text>
|
|
90
|
+
<text fg="#93a1a1"> /queue /dlq Inspect queues</text>
|
|
91
|
+
</box>
|
|
92
|
+
</box>
|
|
93
|
+
|
|
94
|
+
</box>
|
|
53
95
|
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
<text fg="#93a1a1">Ctrl+C - Clear input / Exit</text>
|
|
59
|
-
<text fg="#93a1a1">Ctrl+B - Toggle sidebar</text>
|
|
60
|
-
<text fg="#93a1a1">Ctrl+E - Edit in $EDITOR</text>
|
|
61
|
-
<text fg="#93a1a1">PageUp/Down - Scroll messages</text>
|
|
62
|
-
<text> </text>
|
|
96
|
+
<box flexDirection="column">
|
|
97
|
+
<text fg="#586e75"> m - full manual | any key - dismiss</text>
|
|
98
|
+
<text fg="#2a2a3e"> Ei - 永 (ei) - eternal</text>
|
|
99
|
+
</box>
|
|
63
100
|
|
|
64
|
-
<text fg="#586e75">
|
|
65
|
-
Press any key to dismiss
|
|
66
|
-
</text>
|
|
67
|
-
<text> </text>
|
|
68
|
-
<text fg="#2a2a3e">
|
|
69
|
-
Ei - 永 (ei) - eternal
|
|
70
|
-
</text>
|
|
71
101
|
</box>
|
|
72
102
|
</box>
|
|
73
103
|
);
|
package/tui/src/context/ei.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { Processor } from "../../../src/core/processor.js";
|
|
|
14
14
|
import { FileStorage } from "../storage/file.js";
|
|
15
15
|
import { remoteSync } from "../../../src/storage/remote.js";
|
|
16
16
|
import { logger, clearLog, interceptConsole } from "../util/logger.js";
|
|
17
|
+
import { E2E_SKIP_LOCAL_DETECT } from "../util/e2e-flags.js";
|
|
17
18
|
import { ConflictOverlay } from "../components/ConflictOverlay.js";
|
|
18
19
|
import type {
|
|
19
20
|
Ei_Interface,
|
|
@@ -698,7 +699,10 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
698
699
|
try {
|
|
699
700
|
const human = await processor!.getHuman();
|
|
700
701
|
const hasAccounts = human.settings?.accounts && human.settings.accounts.length > 0;
|
|
701
|
-
if (!hasAccounts) {
|
|
702
|
+
if (!hasAccounts && E2E_SKIP_LOCAL_DETECT) {
|
|
703
|
+
logger.info("E2E_SKIP_LOCAL_DETECT active, skipping local LLM check");
|
|
704
|
+
setShowWelcomeOverlay(true);
|
|
705
|
+
} else if (!hasAccounts) {
|
|
702
706
|
logger.info("No LLM accounts configured, checking for local LLM...");
|
|
703
707
|
try {
|
|
704
708
|
const response = await fetch("http://127.0.0.1:1234/v1/models", {
|
|
@@ -707,6 +711,7 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
707
711
|
});
|
|
708
712
|
if (response.ok) {
|
|
709
713
|
logger.info("Local LLM detected, auto-configuring...");
|
|
714
|
+
const defaultModelId = crypto.randomUUID();
|
|
710
715
|
const localAccount: ProviderAccount = {
|
|
711
716
|
id: crypto.randomUUID(),
|
|
712
717
|
name: "Local LLM",
|
|
@@ -714,13 +719,15 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
714
719
|
url: "http://127.0.0.1:1234/v1",
|
|
715
720
|
enabled: true,
|
|
716
721
|
created_at: new Date().toISOString(),
|
|
722
|
+
default_model: defaultModelId,
|
|
723
|
+
models: [{ id: defaultModelId, name: "(default)" }],
|
|
717
724
|
};
|
|
718
725
|
const currentHuman = await processor!.getHuman();
|
|
719
726
|
await processor!.updateHuman({
|
|
720
727
|
settings: {
|
|
721
728
|
...currentHuman.settings,
|
|
722
729
|
accounts: [localAccount],
|
|
723
|
-
default_model:
|
|
730
|
+
default_model: defaultModelId,
|
|
724
731
|
},
|
|
725
732
|
});
|
|
726
733
|
showNotification("Local LLM detected and configured!", "info");
|
|
@@ -33,8 +33,10 @@ export const OverlayProvider: ParentComponent = (props) => {
|
|
|
33
33
|
const hideOverlay = () => {
|
|
34
34
|
logger.debug("[overlay] hideOverlay called");
|
|
35
35
|
setOverlayRenderer(null);
|
|
36
|
+
cliRenderer?.requestRender();
|
|
36
37
|
};
|
|
37
38
|
setOverlayRenderer(() => () => renderer(hideOverlay, hideForEditor));
|
|
39
|
+
queueMicrotask(() => cliRenderer?.requestRender());
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
const hideOverlay = () => {
|
package/tui/src/index.tsx
CHANGED
|
@@ -4,6 +4,13 @@ import { App } from "./app";
|
|
|
4
4
|
|
|
5
5
|
import { InstanceLock } from "./util/instance-lock";
|
|
6
6
|
import { FileStorage } from "./storage/file";
|
|
7
|
+
import pkg from "../../package.json";
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
if (args.includes("--version") || args.includes("version") || args.includes("-v")) {
|
|
11
|
+
process.stdout.write(`${pkg.version}\n`);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
const storage = new FileStorage(Bun.env.EI_DATA_PATH);
|
|
9
16
|
const lock = new InstanceLock(storage.getDataPath());
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EI_E2E_MODE — bitfield for test seams that can't be solved via data seeding.
|
|
3
|
+
*
|
|
4
|
+
* Use prime-power bits so combinations are unambiguous:
|
|
5
|
+
* 1 — skip local LLM auto-detect (fetch to :1234/:11434)
|
|
6
|
+
* 2 — (reserved for next scenario)
|
|
7
|
+
* 3 — flags 1 + 2 combined
|
|
8
|
+
*
|
|
9
|
+
* Production code should never set this. Tests pass it via env in test.use({ env: { EI_E2E_MODE: "1" } }).
|
|
10
|
+
*/
|
|
11
|
+
const E2E_MODE = parseInt(process.env.EI_E2E_MODE ?? "0", 10);
|
|
12
|
+
|
|
13
|
+
export const E2E_SKIP_LOCAL_DETECT = (E2E_MODE & 1) !== 0;
|
package/tui/src/util/editor.ts
CHANGED
|
@@ -23,6 +23,41 @@ export interface EditorResult {
|
|
|
23
23
|
aborted: boolean;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export async function spawnPager(content: string, renderer: CliRenderer): Promise<void> {
|
|
27
|
+
const pager = process.env.PAGER || "less";
|
|
28
|
+
const tmpDir = os.tmpdir();
|
|
29
|
+
const tmpFile = path.join(tmpDir, `ei-help-${Date.now()}.txt`);
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(tmpFile, content, "utf-8");
|
|
32
|
+
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
34
|
+
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
renderer.suspend();
|
|
37
|
+
renderer.currentRenderBuffer.clear();
|
|
38
|
+
|
|
39
|
+
const child = spawn(pager, [tmpFile], {
|
|
40
|
+
stdio: "inherit",
|
|
41
|
+
shell: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
child.on("error", () => {
|
|
45
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
46
|
+
renderer.resume();
|
|
47
|
+
renderer.requestRender();
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
child.on("exit", () => {
|
|
52
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
53
|
+
renderer.currentRenderBuffer.clear();
|
|
54
|
+
renderer.resume();
|
|
55
|
+
renderer.requestRender();
|
|
56
|
+
resolve();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
26
61
|
export async function spawnEditorRaw(options: EditorRawOptions): Promise<EditorResult> {
|
|
27
62
|
const { initialContent, filename } = options;
|
|
28
63
|
const editor = process.env.EDITOR || process.env.VISUAL || "vi";
|
|
@@ -152,9 +187,7 @@ export async function spawnEditor(options: EditorOptions): Promise<EditorResult>
|
|
|
152
187
|
logger.debug("[editor] already suspended before spawn, skipping resume");
|
|
153
188
|
}
|
|
154
189
|
|
|
155
|
-
|
|
156
|
-
renderer.requestRender();
|
|
157
|
-
});
|
|
190
|
+
renderer.requestRender();
|
|
158
191
|
|
|
159
192
|
if (code !== 0) {
|
|
160
193
|
try { fs.unlinkSync(tmpFile); } catch {}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export function buildManPage(): string {
|
|
2
|
+
return `EI(1) Ei Terminal UI EI(1)
|
|
3
|
+
|
|
4
|
+
NAME
|
|
5
|
+
ei - local-first AI companion with persistent personas
|
|
6
|
+
|
|
7
|
+
KEYBINDINGS
|
|
8
|
+
Ctrl+E Open current input in $EDITOR (preserves text)
|
|
9
|
+
Ctrl+C Clear input field (second press exits)
|
|
10
|
+
Ctrl+B Toggle sidebar
|
|
11
|
+
Escape Abort operation / resume queue
|
|
12
|
+
PageUp/Down Scroll message history
|
|
13
|
+
|
|
14
|
+
CORE COMMANDS
|
|
15
|
+
/settings, /set
|
|
16
|
+
Edit global settings in $EDITOR. Configure default model,
|
|
17
|
+
heartbeat interval, context window, integrations, and more.
|
|
18
|
+
|
|
19
|
+
/quit, /q
|
|
20
|
+
Save, sync, and exit. Add ! to force quit without syncing: /q!
|
|
21
|
+
|
|
22
|
+
/provider
|
|
23
|
+
Open provider picker to select, edit, or create LLM providers.
|
|
24
|
+
/provider <Name> Set provider on active persona
|
|
25
|
+
/provider <Name>:<Model> Set provider and model explicitly
|
|
26
|
+
/provider new Create a new provider
|
|
27
|
+
|
|
28
|
+
/me
|
|
29
|
+
Edit your personal data (facts, topics, people) in $EDITOR.
|
|
30
|
+
Each section includes a commented stub — uncomment and fill it in
|
|
31
|
+
to create a new entry. No UUID required; one is generated for you.
|
|
32
|
+
|
|
33
|
+
/me fact Edit only facts (stub included)
|
|
34
|
+
/me topic Edit only topics (stub included)
|
|
35
|
+
/me person Edit only people (stub included)
|
|
36
|
+
/me fact new Open with just the new-fact stub
|
|
37
|
+
/me fact coffee Filter facts whose name contains "coffee"
|
|
38
|
+
/me person "New York" Quoted search for multi-word names
|
|
39
|
+
|
|
40
|
+
/details, /d
|
|
41
|
+
Edit the current persona's details in $EDITOR.
|
|
42
|
+
/d <name> Edit a specific persona by name
|
|
43
|
+
|
|
44
|
+
PERSONA COMMANDS
|
|
45
|
+
/persona, /p
|
|
46
|
+
Open persona picker. Switch, list, or create personas.
|
|
47
|
+
/p new <name> Create a new persona
|
|
48
|
+
/p update <name> [person] Regenerate persona from a person record
|
|
49
|
+
|
|
50
|
+
/context, /messages
|
|
51
|
+
Edit which messages are included in LLM context.
|
|
52
|
+
|
|
53
|
+
/pause
|
|
54
|
+
Pause the current persona indefinitely.
|
|
55
|
+
/pause <duration> Pause for a duration: 2h, 1d, 1w
|
|
56
|
+
|
|
57
|
+
/resume, /unpause
|
|
58
|
+
Resume the current paused persona.
|
|
59
|
+
/resume <name> Resume a specific persona
|
|
60
|
+
|
|
61
|
+
/new
|
|
62
|
+
Toggle a context boundary — starts a fresh conversation thread
|
|
63
|
+
without deleting history.
|
|
64
|
+
|
|
65
|
+
/quotes, /quote
|
|
66
|
+
Manage quotes attached to messages.
|
|
67
|
+
/quotes <N> View quotes from message N
|
|
68
|
+
/quotes me View your own quotes
|
|
69
|
+
/quotes search "term" Search quotes by keyword
|
|
70
|
+
/quotes <persona> View a persona's quotes
|
|
71
|
+
|
|
72
|
+
ROOM COMMANDS
|
|
73
|
+
/room, /r
|
|
74
|
+
Open room picker. Switch or create rooms.
|
|
75
|
+
/room new Create a new room (FFA, CYP, or MAP mode)
|
|
76
|
+
/room new <name> Create with a pre-filled name
|
|
77
|
+
|
|
78
|
+
/activate, /a
|
|
79
|
+
Advance the active node in a CYP or MAP room.
|
|
80
|
+
/activate <num> Activate a specific response by number
|
|
81
|
+
|
|
82
|
+
/silence
|
|
83
|
+
Pass your turn in a room with an optional reason.
|
|
84
|
+
/silence [reason]
|
|
85
|
+
|
|
86
|
+
/capture
|
|
87
|
+
Force-extract quotes, topics, and people from the current chat now.
|
|
88
|
+
|
|
89
|
+
EXTENDED COMMANDS
|
|
90
|
+
/tools
|
|
91
|
+
Manage tool providers — enable or disable tools per persona.
|
|
92
|
+
|
|
93
|
+
/auth
|
|
94
|
+
Authenticate with an external service.
|
|
95
|
+
/auth spotify Connect your Spotify account
|
|
96
|
+
|
|
97
|
+
/queue
|
|
98
|
+
Pause the queue and inspect or edit active items in $EDITOR.
|
|
99
|
+
|
|
100
|
+
/dlq
|
|
101
|
+
Inspect and recover failed (dead-letter) queue items in $EDITOR.
|
|
102
|
+
|
|
103
|
+
/dedupe
|
|
104
|
+
Find and merge duplicate people or topics.
|
|
105
|
+
/dedupe person Flare "Jeremy Scherer"
|
|
106
|
+
/dedupe topic AI "artificial intelligence"
|
|
107
|
+
|
|
108
|
+
/archive
|
|
109
|
+
Archive a persona or room. Lists archived items if no name given.
|
|
110
|
+
/archive <name> Archive by name
|
|
111
|
+
|
|
112
|
+
/unarchive
|
|
113
|
+
Restore an archived persona or room and switch to it.
|
|
114
|
+
/unarchive <name>
|
|
115
|
+
|
|
116
|
+
/delete, /del
|
|
117
|
+
Permanently delete a persona. Cannot be undone.
|
|
118
|
+
/delete <name>
|
|
119
|
+
|
|
120
|
+
/setsync, /ss
|
|
121
|
+
Set sync credentials (triggers restart).
|
|
122
|
+
/setsync <username> <passphrase>
|
|
123
|
+
|
|
124
|
+
/editor, /e, /edit
|
|
125
|
+
Open $EDITOR with the current input field contents.
|
|
126
|
+
Note: Ctrl+E does the same thing without clearing the input first.
|
|
127
|
+
|
|
128
|
+
TIPS
|
|
129
|
+
- Append ! to any command as shorthand for --force: /quit!
|
|
130
|
+
- Duration strings: 30m, 2h, 1d, 1w (used by /pause, /settings)
|
|
131
|
+
- All editor fields that say "null" inherit from your global settings
|
|
132
|
+
- $EDITOR and $PAGER are respected throughout
|
|
133
|
+
|
|
134
|
+
Ei - 永 (ei) - eternal
|
|
135
|
+
`;
|
|
136
|
+
}
|