@prestyj/cli 4.3.15 → 4.3.54
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/README.md +4 -4
- package/dist/cli.js +529 -74
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +122 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +41 -23
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +14 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/auto-update.d.ts +24 -3
- package/dist/core/auto-update.d.ts.map +1 -1
- package/dist/core/auto-update.js +143 -55
- package/dist/core/auto-update.js.map +1 -1
- package/dist/core/auto-update.test.d.ts +2 -0
- package/dist/core/auto-update.test.d.ts.map +1 -0
- package/dist/core/auto-update.test.js +205 -0
- package/dist/core/auto-update.test.js.map +1 -0
- package/dist/core/compaction/compactor.d.ts +4 -0
- package/dist/core/compaction/compactor.d.ts.map +1 -1
- package/dist/core/compaction/compactor.js +17 -1
- package/dist/core/compaction/compactor.js.map +1 -1
- package/dist/core/compaction/compactor.test.js +44 -11
- package/dist/core/compaction/compactor.test.js.map +1 -1
- package/dist/core/compaction/token-estimator.d.ts.map +1 -1
- package/dist/core/compaction/token-estimator.js +14 -1
- package/dist/core/compaction/token-estimator.js.map +1 -1
- package/dist/core/compaction/token-estimator.test.js +1 -1
- package/dist/core/logger.d.ts +7 -2
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +48 -4
- package/dist/core/logger.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +56 -21
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/prompt-commands.d.ts.map +1 -1
- package/dist/core/prompt-commands.js +356 -1
- package/dist/core/prompt-commands.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +29 -14
- package/dist/core/skills.js.map +1 -1
- package/dist/modes/agent-home-mode.d.ts +21 -0
- package/dist/modes/agent-home-mode.d.ts.map +1 -0
- package/dist/modes/agent-home-mode.js +553 -0
- package/dist/modes/agent-home-mode.js.map +1 -0
- package/dist/system-prompt.d.ts +5 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +118 -82
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/bash.js +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +4 -2
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +61 -33
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/edit.test.js +80 -20
- package/dist/tools/edit.test.js.map +1 -1
- package/dist/tools/grep.d.ts.map +1 -1
- package/dist/tools/grep.js +12 -0
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/plan-mode.test.js +22 -6
- package/dist/tools/plan-mode.test.js.map +1 -1
- package/dist/tools/prompt-hints.d.ts +10 -0
- package/dist/tools/prompt-hints.d.ts.map +1 -0
- package/dist/tools/prompt-hints.js +45 -0
- package/dist/tools/prompt-hints.js.map +1 -0
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +32 -6
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/read.test.js +23 -4
- package/dist/tools/read.test.js.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +20 -3
- package/dist/tools/subagent.js.map +1 -1
- package/dist/tools/write.test.js +13 -1
- package/dist/tools/write.test.js.map +1 -1
- package/dist/ui/App.d.ts +6 -1
- package/dist/ui/App.d.ts.map +1 -1
- package/dist/ui/App.js +195 -70
- package/dist/ui/App.js.map +1 -1
- package/dist/ui/components/ActivityIndicator.d.ts.map +1 -1
- package/dist/ui/components/ActivityIndicator.js +0 -1
- package/dist/ui/components/ActivityIndicator.js.map +1 -1
- package/dist/ui/components/EyesOverlay.d.ts +10 -0
- package/dist/ui/components/EyesOverlay.d.ts.map +1 -0
- package/dist/ui/components/EyesOverlay.js +220 -0
- package/dist/ui/components/EyesOverlay.js.map +1 -0
- package/dist/ui/components/Footer.d.ts.map +1 -1
- package/dist/ui/components/Footer.js +7 -6
- package/dist/ui/components/Footer.js.map +1 -1
- package/dist/ui/components/InputArea.d.ts +4 -1
- package/dist/ui/components/InputArea.d.ts.map +1 -1
- package/dist/ui/components/InputArea.js +6 -5
- package/dist/ui/components/InputArea.js.map +1 -1
- package/dist/ui/components/ModelSelector.d.ts.map +1 -1
- package/dist/ui/components/ModelSelector.js +2 -1
- package/dist/ui/components/ModelSelector.js.map +1 -1
- package/dist/ui/components/PlanOverlay.d.ts.map +1 -1
- package/dist/ui/components/PlanOverlay.js +7 -1
- package/dist/ui/components/PlanOverlay.js.map +1 -1
- package/dist/ui/components/SelectList.d.ts +3 -1
- package/dist/ui/components/SelectList.d.ts.map +1 -1
- package/dist/ui/components/SelectList.js +15 -2
- package/dist/ui/components/SelectList.js.map +1 -1
- package/dist/ui/components/SkillsOverlay.d.ts.map +1 -1
- package/dist/ui/components/SkillsOverlay.js +6 -59
- package/dist/ui/components/SkillsOverlay.js.map +1 -1
- package/dist/ui/components/SlashCommandMenu.d.ts.map +1 -1
- package/dist/ui/components/SlashCommandMenu.js +18 -8
- package/dist/ui/components/SlashCommandMenu.js.map +1 -1
- package/dist/ui/hooks/stall-retry-duplicate.test.d.ts +2 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.d.ts.map +1 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.js +94 -0
- package/dist/ui/hooks/stall-retry-duplicate.test.js.map +1 -0
- package/dist/ui/hooks/useAgentLoop.d.ts +1 -1
- package/dist/ui/hooks/useAgentLoop.d.ts.map +1 -1
- package/dist/ui/hooks/useAgentLoop.js +19 -0
- package/dist/ui/hooks/useAgentLoop.js.map +1 -1
- package/dist/ui/login.d.ts.map +1 -1
- package/dist/ui/login.js +6 -5
- package/dist/ui/login.js.map +1 -1
- package/dist/ui/stores/create-store.d.ts +15 -0
- package/dist/ui/stores/create-store.d.ts.map +1 -0
- package/dist/ui/stores/create-store.js +30 -0
- package/dist/ui/stores/create-store.js.map +1 -0
- package/dist/ui/stores/index.d.ts +3 -0
- package/dist/ui/stores/index.d.ts.map +1 -0
- package/dist/ui/stores/index.js +3 -0
- package/dist/ui/stores/index.js.map +1 -0
- package/dist/ui/stores/taskbar-store.d.ts +19 -0
- package/dist/ui/stores/taskbar-store.d.ts.map +1 -0
- package/dist/ui/stores/taskbar-store.js +59 -0
- package/dist/ui/stores/taskbar-store.js.map +1 -0
- package/dist/ui/utils/token-to-ansi.d.ts.map +1 -1
- package/dist/ui/utils/token-to-ansi.js +9 -1
- package/dist/ui/utils/token-to-ansi.js.map +1 -1
- package/dist/utils/image.d.ts +18 -1
- package/dist/utils/image.d.ts.map +1 -1
- package/dist/utils/image.js +80 -31
- package/dist/utils/image.js.map +1 -1
- package/dist/utils/session-title.js +1 -1
- package/package.json +13 -9
package/dist/core/skills.js
CHANGED
|
@@ -18,27 +18,42 @@ export async function discoverSkills(options) {
|
|
|
18
18
|
}
|
|
19
19
|
async function loadSkillsFromDir(dir, source) {
|
|
20
20
|
const skills = [];
|
|
21
|
-
let
|
|
21
|
+
let dirents;
|
|
22
22
|
try {
|
|
23
|
-
|
|
23
|
+
dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
24
24
|
}
|
|
25
25
|
catch {
|
|
26
26
|
return skills;
|
|
27
27
|
}
|
|
28
|
-
for (const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
for (const entry of dirents) {
|
|
29
|
+
const entryPath = path.join(dir, entry.name);
|
|
30
|
+
// Flat layout: ~/.ezcoder/skills/foo.md
|
|
31
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
32
|
+
try {
|
|
33
|
+
const content = await fs.readFile(entryPath, "utf-8");
|
|
34
|
+
const skill = parseSkillFile(content, source);
|
|
35
|
+
if (!skill.name)
|
|
36
|
+
skill.name = path.basename(entry.name, ".md");
|
|
37
|
+
skills.push(skill);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Skip unreadable files
|
|
37
41
|
}
|
|
38
|
-
|
|
42
|
+
continue;
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// Directory layout (skills.sh ecosystem): ~/.ezcoder/skills/foo/SKILL.md
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
const skillFile = path.join(entryPath, "SKILL.md");
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs.readFile(skillFile, "utf-8");
|
|
49
|
+
const skill = parseSkillFile(content, source);
|
|
50
|
+
if (!skill.name)
|
|
51
|
+
skill.name = entry.name;
|
|
52
|
+
skills.push(skill);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// No SKILL.md — skip
|
|
56
|
+
}
|
|
42
57
|
}
|
|
43
58
|
}
|
|
44
59
|
return skills;
|
package/dist/core/skills.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAGpC;IACC,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,wCAAwC;IACxC,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;IAChF,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAC;IAE7B,6CAA6C;IAC7C,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC7E,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,MAAc;IAC1D,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,IAAI,
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Provider, ThinkingLevel } from "@prestyj/ai";
|
|
2
|
+
export declare const AGENT_HOME_RELAY_URL = "wss://agent-home-relay.buzzbeamaustralia.workers.dev/ws";
|
|
3
|
+
export interface AgentHomeModeOptions {
|
|
4
|
+
provider: Provider;
|
|
5
|
+
model: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
version: string;
|
|
8
|
+
thinkingLevel?: ThinkingLevel;
|
|
9
|
+
agentHome: {
|
|
10
|
+
token: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Agent Home mode: run ezcoder as an Agent Home agent.
|
|
15
|
+
*
|
|
16
|
+
* Connects to the Agent Home relay via WebSocket and bridges
|
|
17
|
+
* messages between the iOS app and the ezcoder agent loop.
|
|
18
|
+
* Each Agent Home session maps to its own AgentSession.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runAgentHomeMode(options: AgentHomeModeOptions): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=agent-home-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-home-mode.d.ts","sourceRoot":"","sources":["../../src/modes/agent-home-mode.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAa3D,eAAO,MAAM,oBAAoB,4DAA4D,CAAC;AAE9F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAqDD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+lBnF"}
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AgentHomeClient } from "@kenkaiiii/agent-home-sdk";
|
|
4
|
+
import { AgentSession } from "../core/agent-session.js";
|
|
5
|
+
import { isAbortError } from "@prestyj/agent";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { formatUserError } from "../utils/error-handler.js";
|
|
8
|
+
import { log, closeLogger } from "../core/logger.js";
|
|
9
|
+
import { getAppPaths } from "../config.js";
|
|
10
|
+
import { MODELS, getContextWindow } from "../core/model-registry.js";
|
|
11
|
+
import { estimateConversationTokens } from "../core/compaction/token-estimator.js";
|
|
12
|
+
import { PROMPT_COMMANDS } from "../core/prompt-commands.js";
|
|
13
|
+
import { loadCustomCommands } from "../core/custom-commands.js";
|
|
14
|
+
export const AGENT_HOME_RELAY_URL = "wss://agent-home-relay.buzzbeamaustralia.workers.dev/ws";
|
|
15
|
+
// ── Project Discovery ──────────────────────────────────────
|
|
16
|
+
async function discoverProjects() {
|
|
17
|
+
const sessionsDir = getAppPaths().sessionsDir;
|
|
18
|
+
try {
|
|
19
|
+
const entries = await fs.readdir(sessionsDir);
|
|
20
|
+
const projects = [];
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const decoded = "/" + entry.replace(/_/g, "/");
|
|
23
|
+
const dir = path.join(sessionsDir, entry);
|
|
24
|
+
const stat = await fs.stat(dir);
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
const files = await fs.readdir(dir);
|
|
27
|
+
if (files.some((f) => f.endsWith(".jsonl"))) {
|
|
28
|
+
projects.push(decoded);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return projects.sort();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ── Commands handled before the agent loop ────────────────
|
|
39
|
+
const HANDLED_COMMANDS = new Set([
|
|
40
|
+
"help",
|
|
41
|
+
"status",
|
|
42
|
+
"cancel",
|
|
43
|
+
"link",
|
|
44
|
+
"unlink",
|
|
45
|
+
"start",
|
|
46
|
+
"new",
|
|
47
|
+
"n",
|
|
48
|
+
"m",
|
|
49
|
+
"model",
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* Agent Home mode: run ezcoder as an Agent Home agent.
|
|
53
|
+
*
|
|
54
|
+
* Connects to the Agent Home relay via WebSocket and bridges
|
|
55
|
+
* messages between the iOS app and the ezcoder agent loop.
|
|
56
|
+
* Each Agent Home session maps to its own AgentSession.
|
|
57
|
+
*/
|
|
58
|
+
export async function runAgentHomeMode(options) {
|
|
59
|
+
const sessionStates = new Map();
|
|
60
|
+
const sessionCreationLocks = new Map();
|
|
61
|
+
const defaultCwd = options.cwd;
|
|
62
|
+
const sessionProjects = new Map();
|
|
63
|
+
const pendingLinkSelections = new Map();
|
|
64
|
+
const pendingModelSelections = new Map();
|
|
65
|
+
const client = new AgentHomeClient({
|
|
66
|
+
relayUrl: AGENT_HOME_RELAY_URL,
|
|
67
|
+
token: options.agentHome.token,
|
|
68
|
+
agent: {
|
|
69
|
+
id: "ezcoder",
|
|
70
|
+
name: "EZ Coder",
|
|
71
|
+
description: `AI coding agent — ${options.model}`,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
// ── Helpers ────────────────────────────────────────────
|
|
75
|
+
/** Send a response, tagging with sessionId for new sessions. */
|
|
76
|
+
function reply(stream, text, isNewSession, targetSessionId) {
|
|
77
|
+
if (isNewSession) {
|
|
78
|
+
stream.end(text, { sessionId: targetSessionId });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
stream.end(text);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ── Session lifecycle ──────────────────────────────────
|
|
85
|
+
function resolveProjectPath(sessionId) {
|
|
86
|
+
if (sessionId && sessionProjects.has(sessionId)) {
|
|
87
|
+
return sessionProjects.get(sessionId);
|
|
88
|
+
}
|
|
89
|
+
return defaultCwd;
|
|
90
|
+
}
|
|
91
|
+
async function createSession(sessionId, cwd) {
|
|
92
|
+
const ac = new AbortController();
|
|
93
|
+
const session = new AgentSession({
|
|
94
|
+
provider: options.provider,
|
|
95
|
+
model: options.model,
|
|
96
|
+
cwd,
|
|
97
|
+
thinkingLevel: options.thinkingLevel,
|
|
98
|
+
signal: ac.signal,
|
|
99
|
+
});
|
|
100
|
+
await session.initialize();
|
|
101
|
+
log("INFO", "agent-home", `Session initialized: ${sessionId}`, { cwd });
|
|
102
|
+
const state = {
|
|
103
|
+
sessionId,
|
|
104
|
+
cwd,
|
|
105
|
+
session,
|
|
106
|
+
ac,
|
|
107
|
+
textBuffer: "",
|
|
108
|
+
activeTools: new Map(),
|
|
109
|
+
isProcessing: false,
|
|
110
|
+
};
|
|
111
|
+
sessionStates.set(sessionId, state);
|
|
112
|
+
return state;
|
|
113
|
+
}
|
|
114
|
+
async function getOrCreateSession(sessionId) {
|
|
115
|
+
const existing = sessionStates.get(sessionId);
|
|
116
|
+
if (existing)
|
|
117
|
+
return existing;
|
|
118
|
+
const pending = sessionCreationLocks.get(sessionId);
|
|
119
|
+
if (pending)
|
|
120
|
+
return pending;
|
|
121
|
+
const cwd = resolveProjectPath(sessionId);
|
|
122
|
+
const promise = createSession(sessionId, cwd);
|
|
123
|
+
sessionCreationLocks.set(sessionId, promise);
|
|
124
|
+
try {
|
|
125
|
+
return await promise;
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
sessionCreationLocks.delete(sessionId);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function switchProject(sessionId, projectPath) {
|
|
132
|
+
const existing = sessionStates.get(sessionId);
|
|
133
|
+
if (existing) {
|
|
134
|
+
await existing.session.dispose();
|
|
135
|
+
sessionStates.delete(sessionId);
|
|
136
|
+
}
|
|
137
|
+
sessionProjects.set(sessionId, projectPath);
|
|
138
|
+
await createSession(sessionId, projectPath);
|
|
139
|
+
}
|
|
140
|
+
function pushSessions() {
|
|
141
|
+
const list = Array.from(sessionStates.values()).map((s) => ({
|
|
142
|
+
id: s.sessionId,
|
|
143
|
+
title: `${path.basename(s.cwd)} — ${s.session.getState().model}`,
|
|
144
|
+
updatedAt: Date.now(),
|
|
145
|
+
}));
|
|
146
|
+
client.updateSessions(list);
|
|
147
|
+
}
|
|
148
|
+
// ── Help text builder ──────────────────────────────────
|
|
149
|
+
async function buildHelpText(sessionId) {
|
|
150
|
+
const state = sessionStates.get(sessionId);
|
|
151
|
+
const currentCwd = state?.cwd ?? resolveProjectPath(sessionId);
|
|
152
|
+
const currentModel = state?.session.getState().model ?? options.model;
|
|
153
|
+
const modelInfo = MODELS.find((m) => m.id === currentModel);
|
|
154
|
+
let text = "";
|
|
155
|
+
text += `**EZ Coder**\n`;
|
|
156
|
+
text += `Project: **${path.basename(currentCwd)}** \u00b7 Model: **${modelInfo?.name ?? currentModel}**\n\n`;
|
|
157
|
+
text += `**Commands**\n`;
|
|
158
|
+
text += `\`/m\` \u2014 Switch model\n`;
|
|
159
|
+
text += `\`/link\` \u2014 Switch project\n`;
|
|
160
|
+
text += `\`/unlink\` \u2014 Reset to default project\n`;
|
|
161
|
+
text += `\`/status\` \u2014 Current state\n`;
|
|
162
|
+
text += `\`/cancel\` \u2014 Abort current task\n`;
|
|
163
|
+
text += `\`/help\` \u2014 This message\n\n`;
|
|
164
|
+
text += `**Session**\n`;
|
|
165
|
+
text += `\`/compact\` \u2014 Compress context\n`;
|
|
166
|
+
text += `\`/new\` \u2014 Fresh session\n`;
|
|
167
|
+
text += `\`/session\` \u2014 List sessions\n`;
|
|
168
|
+
text += `\`/branch\` \u2014 Fork conversation\n`;
|
|
169
|
+
text += `\`/branches\` \u2014 List branches\n`;
|
|
170
|
+
text += `\`/clear\` \u2014 Clear session\n`;
|
|
171
|
+
text += `\`/settings\` \u2014 Show/modify settings\n`;
|
|
172
|
+
if (PROMPT_COMMANDS.length > 0) {
|
|
173
|
+
text += `\n**Agent**\n`;
|
|
174
|
+
for (const cmd of PROMPT_COMMANDS) {
|
|
175
|
+
text += `\`/${cmd.name}\` \u2014 ${cmd.description}\n`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const customCmds = await loadCustomCommands(currentCwd);
|
|
179
|
+
if (customCmds.length > 0) {
|
|
180
|
+
text += `\n**Custom**\n`;
|
|
181
|
+
for (const cmd of customCmds) {
|
|
182
|
+
text += `\`/${cmd.name}\` \u2014 ${cmd.description}\n`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
text += `\n_Send any message to start coding._`;
|
|
186
|
+
return text;
|
|
187
|
+
}
|
|
188
|
+
// ── Message handler ────────────────────────────────────
|
|
189
|
+
client.onMessage(async (message, stream) => {
|
|
190
|
+
const { content, sessionId } = message;
|
|
191
|
+
const trimmed = content.trim();
|
|
192
|
+
let targetSessionId;
|
|
193
|
+
let isNewSession = false;
|
|
194
|
+
if (sessionId) {
|
|
195
|
+
targetSessionId = sessionId;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
targetSessionId = `session-${Date.now()}`;
|
|
199
|
+
isNewSession = true;
|
|
200
|
+
}
|
|
201
|
+
// ── Pending link selection ────────────────────────────
|
|
202
|
+
const pendingProjects = pendingLinkSelections.get(targetSessionId);
|
|
203
|
+
if (pendingProjects) {
|
|
204
|
+
pendingLinkSelections.delete(targetSessionId);
|
|
205
|
+
const num = parseInt(trimmed, 10);
|
|
206
|
+
if (isNaN(num) || num < 1 || num > pendingProjects.length) {
|
|
207
|
+
reply(stream, "Invalid selection. Send `/link` to try again.", isNewSession, targetSessionId);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const selected = pendingProjects[num - 1];
|
|
211
|
+
await switchProject(targetSessionId, selected);
|
|
212
|
+
pushSessions();
|
|
213
|
+
reply(stream, `\u2713 Switched to **${path.basename(selected)}**`, isNewSession, targetSessionId);
|
|
214
|
+
log("INFO", "agent-home", `Linked session ${targetSessionId} to ${selected}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// ── Pending model selection ───────────────────────────
|
|
218
|
+
const pendingModels = pendingModelSelections.get(targetSessionId);
|
|
219
|
+
if (pendingModels) {
|
|
220
|
+
pendingModelSelections.delete(targetSessionId);
|
|
221
|
+
const num = parseInt(trimmed, 10);
|
|
222
|
+
if (isNaN(num) || num < 1 || num > pendingModels.length) {
|
|
223
|
+
reply(stream, "Invalid selection. Send `/m` to try again.", isNewSession, targetSessionId);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const selected = pendingModels[num - 1];
|
|
227
|
+
const state = await getOrCreateSession(targetSessionId);
|
|
228
|
+
await state.session.switchModel(selected.provider, selected.id);
|
|
229
|
+
pushSessions();
|
|
230
|
+
reply(stream, `\u2713 Switched to **${selected.name}**`, isNewSession, targetSessionId);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// ── Command handling ──────────────────────────────────
|
|
234
|
+
if (trimmed.startsWith("/")) {
|
|
235
|
+
const parts = trimmed.split(/\s+/);
|
|
236
|
+
const cmd = parts[0].slice(1).toLowerCase();
|
|
237
|
+
const args = parts.slice(1).join(" ");
|
|
238
|
+
// ── /help ──
|
|
239
|
+
if (cmd === "help" || cmd === "start") {
|
|
240
|
+
const text = await buildHelpText(targetSessionId);
|
|
241
|
+
reply(stream, text, isNewSession, targetSessionId);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// ── /status ──
|
|
245
|
+
if (cmd === "status") {
|
|
246
|
+
const state = sessionStates.get(targetSessionId);
|
|
247
|
+
const currentCwd = state?.cwd ?? resolveProjectPath(targetSessionId);
|
|
248
|
+
const projectName = path.basename(currentCwd);
|
|
249
|
+
if (!state) {
|
|
250
|
+
reply(stream, `**${projectName}**\n\n` +
|
|
251
|
+
`Status: _Not started_\n\n` +
|
|
252
|
+
`_Send a message to initialize._`, isNewSession, targetSessionId);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const sessionState = state.session.getState();
|
|
256
|
+
const modelInfo = MODELS.find((m) => m.id === sessionState.model);
|
|
257
|
+
const contextWindow = getContextWindow(sessionState.model);
|
|
258
|
+
const contextTokens = estimateConversationTokens(state.session.getMessages());
|
|
259
|
+
const pctRaw = (contextTokens / contextWindow) * 100;
|
|
260
|
+
const contextStr = pctRaw > 0 && pctRaw < 1 ? "<1" : String(Math.round(pctRaw));
|
|
261
|
+
reply(stream, `**${projectName}**\n\n` +
|
|
262
|
+
`| | |\n|---|---|\n` +
|
|
263
|
+
`| Model | **${modelInfo?.name ?? sessionState.model}** |\n` +
|
|
264
|
+
`| Messages | ${sessionState.messageCount} |\n` +
|
|
265
|
+
`| Context | ${contextStr}% |\n` +
|
|
266
|
+
`| Status | ${state.isProcessing ? "Working..." : "Idle"} |`, isNewSession, targetSessionId);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// ── /cancel ──
|
|
270
|
+
if (cmd === "cancel") {
|
|
271
|
+
const state = sessionStates.get(targetSessionId);
|
|
272
|
+
if (state?.isProcessing) {
|
|
273
|
+
state.ac.abort();
|
|
274
|
+
const newAc = new AbortController();
|
|
275
|
+
state.ac = newAc;
|
|
276
|
+
state.session.setSignal(newAc.signal);
|
|
277
|
+
reply(stream, "\u2713 Cancelled.", isNewSession, targetSessionId);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
reply(stream, "_Nothing to cancel._", isNewSession, targetSessionId);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
// ── /new, /n ──
|
|
285
|
+
if (cmd === "new" || cmd === "n") {
|
|
286
|
+
const state = await getOrCreateSession(targetSessionId);
|
|
287
|
+
await state.session.newSession();
|
|
288
|
+
pushSessions();
|
|
289
|
+
reply(stream, "\u2713 **New session started.**", isNewSession, targetSessionId);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// ── /m, /model ──
|
|
293
|
+
if (cmd === "m" || cmd === "model") {
|
|
294
|
+
const state = sessionStates.get(targetSessionId);
|
|
295
|
+
const currentModel = state?.session.getState().model;
|
|
296
|
+
if (args) {
|
|
297
|
+
// Direct switch: /m 3 (number) or /m opus (name fragment)
|
|
298
|
+
const num = parseInt(args, 10);
|
|
299
|
+
if (!isNaN(num) && num >= 1 && num <= MODELS.length) {
|
|
300
|
+
const selected = MODELS[num - 1];
|
|
301
|
+
const s = await getOrCreateSession(targetSessionId);
|
|
302
|
+
await s.session.switchModel(selected.provider, selected.id);
|
|
303
|
+
pushSessions();
|
|
304
|
+
reply(stream, `\u2713 Switched to **${selected.name}**`, isNewSession, targetSessionId);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
// Try matching by name fragment
|
|
308
|
+
const lower = args.toLowerCase();
|
|
309
|
+
const match = MODELS.find((m) => m.name.toLowerCase().includes(lower) || m.id.toLowerCase().includes(lower));
|
|
310
|
+
if (match) {
|
|
311
|
+
const s = await getOrCreateSession(targetSessionId);
|
|
312
|
+
await s.session.switchModel(match.provider, match.id);
|
|
313
|
+
pushSessions();
|
|
314
|
+
reply(stream, `\u2713 Switched to **${match.name}**`, isNewSession, targetSessionId);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
reply(stream, `No model matching "${args}". Send \`/m\` to see the list.`, isNewSession, targetSessionId);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
// No args — show numbered list
|
|
321
|
+
let listText = `**Models**\n\n`;
|
|
322
|
+
const modelList = [...MODELS];
|
|
323
|
+
modelList.forEach((m, i) => {
|
|
324
|
+
const active = m.id === currentModel ? " \u2190" : "";
|
|
325
|
+
listText += `${i + 1}. ${m.name}${active}\n`;
|
|
326
|
+
});
|
|
327
|
+
pendingModelSelections.set(targetSessionId, modelList);
|
|
328
|
+
listText += `\n_Send number or name to switch._`;
|
|
329
|
+
reply(stream, listText, isNewSession, targetSessionId);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// ── /link ──
|
|
333
|
+
if (cmd === "link") {
|
|
334
|
+
if (args && path.isAbsolute(args.trim())) {
|
|
335
|
+
const projectPath = args.trim();
|
|
336
|
+
await switchProject(targetSessionId, projectPath);
|
|
337
|
+
pushSessions();
|
|
338
|
+
reply(stream, `\u2713 Switched to **${path.basename(projectPath)}**`, isNewSession, targetSessionId);
|
|
339
|
+
log("INFO", "agent-home", `Linked session ${targetSessionId} to ${projectPath}`);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const projects = await discoverProjects();
|
|
343
|
+
if (projects.length === 0) {
|
|
344
|
+
reply(stream, "No projects found.\n\nUse `/link <path>` to link manually.", isNewSession, targetSessionId);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const currentCwd = sessionStates.get(targetSessionId)?.cwd ?? resolveProjectPath(targetSessionId);
|
|
348
|
+
const lines = projects.map((p, i) => {
|
|
349
|
+
const name = path.basename(p);
|
|
350
|
+
const marker = p === currentCwd ? " \u2190" : "";
|
|
351
|
+
return `${i + 1}. **${name}**${marker}`;
|
|
352
|
+
});
|
|
353
|
+
pendingLinkSelections.set(targetSessionId, projects);
|
|
354
|
+
reply(stream, `**Projects**\n\n${lines.join("\n")}\n\n_Send the number to switch._`, isNewSession, targetSessionId);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
// ── /unlink ──
|
|
358
|
+
if (cmd === "unlink") {
|
|
359
|
+
if (!sessionProjects.has(targetSessionId)) {
|
|
360
|
+
reply(stream, "_This session isn't linked to a project._", isNewSession, targetSessionId);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const existing = sessionStates.get(targetSessionId);
|
|
364
|
+
if (existing) {
|
|
365
|
+
await existing.session.dispose();
|
|
366
|
+
sessionStates.delete(targetSessionId);
|
|
367
|
+
}
|
|
368
|
+
sessionProjects.delete(targetSessionId);
|
|
369
|
+
pushSessions();
|
|
370
|
+
reply(stream, `\u2713 Unlinked. Reset to **${path.basename(defaultCwd)}**`, isNewSession, targetSessionId);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
// ── Non-handled slash commands → forward to agent loop ──
|
|
374
|
+
if (!HANDLED_COMMANDS.has(cmd)) {
|
|
375
|
+
// Falls through to the agent loop below
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Shouldn't reach here, but guard against it
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ── Forward to agent loop ────────────────────────────
|
|
383
|
+
const state = await getOrCreateSession(targetSessionId);
|
|
384
|
+
if (state.isProcessing) {
|
|
385
|
+
stream.error("EZ Coder is still processing a previous message. Please wait.");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
state.isProcessing = true;
|
|
389
|
+
state.textBuffer = "";
|
|
390
|
+
state.activeTools = new Map();
|
|
391
|
+
const unsubs = [];
|
|
392
|
+
const bus = state.session.eventBus;
|
|
393
|
+
unsubs.push(bus.on("text_delta", ({ text }) => {
|
|
394
|
+
state.textBuffer += text;
|
|
395
|
+
stream.token(text);
|
|
396
|
+
}));
|
|
397
|
+
unsubs.push(bus.on("tool_call_start", ({ toolCallId, name, args: toolArgs }) => {
|
|
398
|
+
state.activeTools.set(toolCallId, { name, startTime: Date.now(), args: toolArgs });
|
|
399
|
+
}));
|
|
400
|
+
unsubs.push(bus.on("tool_call_end", ({ toolCallId, isError, durationMs }) => {
|
|
401
|
+
const tool = state.activeTools.get(toolCallId);
|
|
402
|
+
state.activeTools.delete(toolCallId);
|
|
403
|
+
if (tool) {
|
|
404
|
+
const icon = isError ? "\u2717" : "\u2713";
|
|
405
|
+
const argsStr = formatArgs(tool.args);
|
|
406
|
+
const duration = formatDuration(durationMs);
|
|
407
|
+
const argsPart = argsStr ? ` \`${argsStr}\`` : "";
|
|
408
|
+
stream.token(`\n${icon} **${tool.name}**${argsPart} _${duration}_\n`);
|
|
409
|
+
}
|
|
410
|
+
}));
|
|
411
|
+
unsubs.push(bus.on("compaction_end", ({ originalCount, newCount }) => {
|
|
412
|
+
stream.token(`\n\u2713 **Compacted** ${originalCount} \u2192 ${newCount} messages\n`);
|
|
413
|
+
}));
|
|
414
|
+
try {
|
|
415
|
+
await state.session.prompt(content);
|
|
416
|
+
const finalText = state.textBuffer.trim() || "Done.";
|
|
417
|
+
const modelId = state.session.getState().model;
|
|
418
|
+
const contextWindow = getContextWindow(modelId);
|
|
419
|
+
const contextTokens = estimateConversationTokens(state.session.getMessages());
|
|
420
|
+
const contextPctRaw = (contextTokens / contextWindow) * 100;
|
|
421
|
+
const contextStr = contextPctRaw > 0 && contextPctRaw < 1 ? "<1" : String(Math.round(contextPctRaw));
|
|
422
|
+
const footer = `\n\n---\n_${contextStr}% context_`;
|
|
423
|
+
reply(stream, finalText + footer, isNewSession, targetSessionId);
|
|
424
|
+
pushSessions();
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
if (isAbortError(err)) {
|
|
428
|
+
reply(stream, "Cancelled.", isNewSession, targetSessionId);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
stream.error(`Error: ${formatUserError(err)}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
for (const off of unsubs)
|
|
436
|
+
off();
|
|
437
|
+
state.isProcessing = false;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
// ── Session deletion ───────────────────────────────────
|
|
441
|
+
client.onSessionDelete((sessionId) => {
|
|
442
|
+
const state = sessionStates.get(sessionId);
|
|
443
|
+
if (state) {
|
|
444
|
+
state.session.dispose();
|
|
445
|
+
sessionStates.delete(sessionId);
|
|
446
|
+
sessionProjects.delete(sessionId);
|
|
447
|
+
pendingLinkSelections.delete(sessionId);
|
|
448
|
+
pendingModelSelections.delete(sessionId);
|
|
449
|
+
}
|
|
450
|
+
log("INFO", "agent-home", `Session deleted: ${sessionId}`);
|
|
451
|
+
});
|
|
452
|
+
// ── Connection events ──────────────────────────────────
|
|
453
|
+
client.onConnect(() => {
|
|
454
|
+
log("INFO", "agent-home", "Connected to relay");
|
|
455
|
+
console.log(chalk.hex("#4ade80")(" Connected to Agent Home relay."));
|
|
456
|
+
pushSessions();
|
|
457
|
+
});
|
|
458
|
+
client.onDisconnect(() => {
|
|
459
|
+
log("INFO", "agent-home", "Disconnected from relay (will auto-reconnect)");
|
|
460
|
+
console.log(chalk.hex("#ef4444")(" Disconnected from relay. Reconnecting..."));
|
|
461
|
+
});
|
|
462
|
+
// ── Initialize and start ─────────────────────────────
|
|
463
|
+
try {
|
|
464
|
+
process.stdout.write("\x1b[2J\x1b[3J\x1b[H");
|
|
465
|
+
const modelInfo = MODELS.find((m) => m.id === options.model);
|
|
466
|
+
const modelName = modelInfo?.name ?? options.model;
|
|
467
|
+
const home = process.env.HOME ?? "";
|
|
468
|
+
const displayPath = home && options.cwd.startsWith(home) ? "~" + options.cwd.slice(home.length) : options.cwd;
|
|
469
|
+
const LOGO = [
|
|
470
|
+
" \u2584\u2580\u2580\u2580 \u2584\u2580\u2580\u2580",
|
|
471
|
+
" \u2588 \u2580\u2588 \u2588 \u2580\u2588",
|
|
472
|
+
" \u2580\u2584\u2584\u2580 \u2580\u2584\u2584\u2580",
|
|
473
|
+
];
|
|
474
|
+
const GRADIENT = [
|
|
475
|
+
"#60a5fa",
|
|
476
|
+
"#6da1f9",
|
|
477
|
+
"#7a9df7",
|
|
478
|
+
"#8799f5",
|
|
479
|
+
"#9495f3",
|
|
480
|
+
"#a18ff1",
|
|
481
|
+
"#a78bfa",
|
|
482
|
+
"#a18ff1",
|
|
483
|
+
"#9495f3",
|
|
484
|
+
"#8799f5",
|
|
485
|
+
"#7a9df7",
|
|
486
|
+
"#6da1f9",
|
|
487
|
+
];
|
|
488
|
+
function gradientText(text) {
|
|
489
|
+
let colorIdx = 0;
|
|
490
|
+
return text
|
|
491
|
+
.split("")
|
|
492
|
+
.map((ch) => {
|
|
493
|
+
if (ch === " ")
|
|
494
|
+
return ch;
|
|
495
|
+
const color = GRADIENT[colorIdx++ % GRADIENT.length];
|
|
496
|
+
return chalk.hex(color)(ch);
|
|
497
|
+
})
|
|
498
|
+
.join("");
|
|
499
|
+
}
|
|
500
|
+
const GAP = " ";
|
|
501
|
+
console.log();
|
|
502
|
+
console.log(` ${gradientText(LOGO[0])}${GAP}` +
|
|
503
|
+
chalk.hex("#60a5fa").bold("EZ Coder") +
|
|
504
|
+
chalk.hex("#6b7280")(` v${options.version}`) +
|
|
505
|
+
chalk.hex("#6b7280")(" \u00b7 By ") +
|
|
506
|
+
chalk.white.bold("Nolan Grout"));
|
|
507
|
+
console.log(` ${gradientText(LOGO[1])}${GAP}` + chalk.hex("#a78bfa")(modelName));
|
|
508
|
+
console.log(` ${gradientText(LOGO[2])}${GAP}` + chalk.hex("#6b7280")(displayPath));
|
|
509
|
+
console.log();
|
|
510
|
+
console.log(chalk.hex("#6b7280")(" Mode ") +
|
|
511
|
+
chalk.hex("#a78bfa")("Agent Home") +
|
|
512
|
+
chalk.hex("#6b7280")(" \u00b7 Agent ") +
|
|
513
|
+
chalk.white("EZ Coder"));
|
|
514
|
+
console.log();
|
|
515
|
+
console.log(chalk.hex("#6b7280")(" Connecting to relay..."));
|
|
516
|
+
console.log();
|
|
517
|
+
const shutdown = async () => {
|
|
518
|
+
console.log("\nShutting down...");
|
|
519
|
+
client.disconnect();
|
|
520
|
+
for (const state of sessionStates.values()) {
|
|
521
|
+
await state.session.dispose();
|
|
522
|
+
}
|
|
523
|
+
closeLogger();
|
|
524
|
+
process.exit(0);
|
|
525
|
+
};
|
|
526
|
+
process.on("SIGINT", shutdown);
|
|
527
|
+
process.on("SIGTERM", shutdown);
|
|
528
|
+
client.connect();
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
console.error(`Failed to start: ${formatUserError(err)}`);
|
|
532
|
+
for (const state of sessionStates.values()) {
|
|
533
|
+
await state.session.dispose();
|
|
534
|
+
}
|
|
535
|
+
closeLogger();
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// ── Helpers ───────────────────────────────────────────────
|
|
540
|
+
function formatArgs(args) {
|
|
541
|
+
const entries = Object.entries(args);
|
|
542
|
+
if (entries.length === 0)
|
|
543
|
+
return "";
|
|
544
|
+
const [_key, value] = entries[0];
|
|
545
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
546
|
+
return str.length > 50 ? str.slice(0, 47) + "..." : str;
|
|
547
|
+
}
|
|
548
|
+
function formatDuration(ms) {
|
|
549
|
+
if (ms < 1000)
|
|
550
|
+
return `${ms}ms`;
|
|
551
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
552
|
+
}
|
|
553
|
+
//# sourceMappingURL=agent-home-mode.js.map
|