exempclaw 0.4.0
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/LICENSE +21 -0
- package/README.md +306 -0
- package/dist/agent/agent.d.ts +91 -0
- package/dist/agent/agent.js +258 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/config.d.ts +49 -0
- package/dist/agent/config.js +58 -0
- package/dist/agent/config.js.map +1 -0
- package/dist/agent/persona.d.ts +39 -0
- package/dist/agent/persona.js +81 -0
- package/dist/agent/persona.js.map +1 -0
- package/dist/agents/registry.d.ts +21 -0
- package/dist/agents/registry.js +51 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/cli/approve.d.ts +17 -0
- package/dist/cli/approve.js +50 -0
- package/dist/cli/approve.js.map +1 -0
- package/dist/cli/chat.d.ts +16 -0
- package/dist/cli/chat.js +148 -0
- package/dist/cli/chat.js.map +1 -0
- package/dist/cli/demo.d.ts +7 -0
- package/dist/cli/demo.js +82 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/init.d.ts +17 -0
- package/dist/cli/init.js +89 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/live.d.ts +10 -0
- package/dist/cli/live.js +109 -0
- package/dist/cli/live.js.map +1 -0
- package/dist/cli/offline.d.ts +23 -0
- package/dist/cli/offline.js +236 -0
- package/dist/cli/offline.js.map +1 -0
- package/dist/cli/probe.d.ts +23 -0
- package/dist/cli/probe.js +140 -0
- package/dist/cli/probe.js.map +1 -0
- package/dist/cli/render.d.ts +15 -0
- package/dist/cli/render.js +50 -0
- package/dist/cli/render.js.map +1 -0
- package/dist/cli/tui.d.ts +101 -0
- package/dist/cli/tui.js +334 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/config/index.d.ts +33 -0
- package/dist/config/index.js +48 -0
- package/dist/config/index.js.map +1 -0
- package/dist/connectors/connector.d.ts +58 -0
- package/dist/connectors/connector.js +30 -0
- package/dist/connectors/connector.js.map +1 -0
- package/dist/connectors/email/email-connector.d.ts +43 -0
- package/dist/connectors/email/email-connector.js +364 -0
- package/dist/connectors/email/email-connector.js.map +1 -0
- package/dist/connectors/github/github-connector.d.ts +52 -0
- package/dist/connectors/github/github-connector.js +271 -0
- package/dist/connectors/github/github-connector.js.map +1 -0
- package/dist/connectors/http.d.ts +34 -0
- package/dist/connectors/http.js +78 -0
- package/dist/connectors/http.js.map +1 -0
- package/dist/connectors/index.d.ts +34 -0
- package/dist/connectors/index.js +86 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors/notion/notion-connector.d.ts +45 -0
- package/dist/connectors/notion/notion-connector.js +222 -0
- package/dist/connectors/notion/notion-connector.js.map +1 -0
- package/dist/connectors/slack/slack-connector.d.ts +43 -0
- package/dist/connectors/slack/slack-connector.js +291 -0
- package/dist/connectors/slack/slack-connector.js.map +1 -0
- package/dist/core/errors.d.ts +36 -0
- package/dist/core/errors.js +40 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/logger.d.ts +14 -0
- package/dist/core/logger.js +44 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/run-log.d.ts +37 -0
- package/dist/core/run-log.js +37 -0
- package/dist/core/run-log.js.map +1 -0
- package/dist/core/usage.d.ts +22 -0
- package/dist/core/usage.js +58 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/dashboard/data.d.ts +62 -0
- package/dist/dashboard/data.js +84 -0
- package/dist/dashboard/data.js.map +1 -0
- package/dist/dashboard/page.d.ts +9 -0
- package/dist/dashboard/page.js +421 -0
- package/dist/dashboard/page.js.map +1 -0
- package/dist/dashboard/server.d.ts +19 -0
- package/dist/dashboard/server.js +44 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/demo/bootstrap.d.ts +25 -0
- package/dist/demo/bootstrap.js +60 -0
- package/dist/demo/bootstrap.js.map +1 -0
- package/dist/demo/claude.d.ts +31 -0
- package/dist/demo/claude.js +230 -0
- package/dist/demo/claude.js.map +1 -0
- package/dist/demo/demo-connector.d.ts +19 -0
- package/dist/demo/demo-connector.js +168 -0
- package/dist/demo/demo-connector.js.map +1 -0
- package/dist/demo/world.d.ts +60 -0
- package/dist/demo/world.js +117 -0
- package/dist/demo/world.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +396 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/ingest.d.ts +63 -0
- package/dist/ingest/ingest.js +258 -0
- package/dist/ingest/ingest.js.map +1 -0
- package/dist/llm/claude.d.ts +97 -0
- package/dist/llm/claude.js +163 -0
- package/dist/llm/claude.js.map +1 -0
- package/dist/memory/compaction.d.ts +22 -0
- package/dist/memory/compaction.js +79 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/file-store.d.ts +28 -0
- package/dist/memory/file-store.js +110 -0
- package/dist/memory/file-store.js.map +1 -0
- package/dist/memory/store.d.ts +32 -0
- package/dist/memory/store.js +2 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts +63 -0
- package/dist/orchestrator/orchestrator.js +181 -0
- package/dist/orchestrator/orchestrator.js.map +1 -0
- package/dist/orchestrator/scheduler.d.ts +33 -0
- package/dist/orchestrator/scheduler.js +67 -0
- package/dist/orchestrator/scheduler.js.map +1 -0
- package/dist/orchestrator/seen-events.d.ts +21 -0
- package/dist/orchestrator/seen-events.js +71 -0
- package/dist/orchestrator/seen-events.js.map +1 -0
- package/dist/plugins/apply.d.ts +9 -0
- package/dist/plugins/apply.js +17 -0
- package/dist/plugins/apply.js.map +1 -0
- package/dist/plugins/define.d.ts +29 -0
- package/dist/plugins/define.js +30 -0
- package/dist/plugins/define.js.map +1 -0
- package/dist/plugins/loader.d.ts +31 -0
- package/dist/plugins/loader.js +61 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/scaffold.d.ts +5 -0
- package/dist/plugins/scaffold.js +72 -0
- package/dist/plugins/scaffold.js.map +1 -0
- package/dist/tools/builtin.d.ts +8 -0
- package/dist/tools/builtin.js +63 -0
- package/dist/tools/builtin.js.map +1 -0
- package/dist/tools/tool.d.ts +84 -0
- package/dist/tools/tool.js +70 -0
- package/dist/tools/tool.js.map +1 -0
- package/dist/ui/agent-view.test.d.ts +1 -0
- package/dist/ui/agent-view.test.js +54 -0
- package/dist/ui/agent-view.test.js.map +1 -0
- package/dist/ui/agents-data.d.ts +7 -0
- package/dist/ui/agents-data.js +25 -0
- package/dist/ui/agents-data.js.map +1 -0
- package/dist/ui/app.d.ts +24 -0
- package/dist/ui/app.js +59 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/app.test.d.ts +1 -0
- package/dist/ui/app.test.js +47 -0
- package/dist/ui/app.test.js.map +1 -0
- package/dist/ui/components/key-hints.d.ts +4 -0
- package/dist/ui/components/key-hints.js +6 -0
- package/dist/ui/components/key-hints.js.map +1 -0
- package/dist/ui/components/menu.d.ts +11 -0
- package/dist/ui/components/menu.js +20 -0
- package/dist/ui/components/menu.js.map +1 -0
- package/dist/ui/create-wizard.test.d.ts +1 -0
- package/dist/ui/create-wizard.test.js +58 -0
- package/dist/ui/create-wizard.test.js.map +1 -0
- package/dist/ui/doctor-data.d.ts +6 -0
- package/dist/ui/doctor-data.js +29 -0
- package/dist/ui/doctor-data.js.map +1 -0
- package/dist/ui/history-data.d.ts +2 -0
- package/dist/ui/history-data.js +18 -0
- package/dist/ui/history-data.js.map +1 -0
- package/dist/ui/screens/agent.d.ts +8 -0
- package/dist/ui/screens/agent.js +95 -0
- package/dist/ui/screens/agent.js.map +1 -0
- package/dist/ui/screens/agents.d.ts +7 -0
- package/dist/ui/screens/agents.js +47 -0
- package/dist/ui/screens/agents.js.map +1 -0
- package/dist/ui/screens/create.d.ts +7 -0
- package/dist/ui/screens/create.js +141 -0
- package/dist/ui/screens/create.js.map +1 -0
- package/dist/ui/screens/doctor.d.ts +5 -0
- package/dist/ui/screens/doctor.js +13 -0
- package/dist/ui/screens/doctor.js.map +1 -0
- package/dist/ui/screens/history.d.ts +7 -0
- package/dist/ui/screens/history.js +50 -0
- package/dist/ui/screens/history.js.map +1 -0
- package/dist/ui/screens/home.d.ts +8 -0
- package/dist/ui/screens/home.js +35 -0
- package/dist/ui/screens/home.js.map +1 -0
- package/dist/ui/screens/plugins.d.ts +7 -0
- package/dist/ui/screens/plugins.js +40 -0
- package/dist/ui/screens/plugins.js.map +1 -0
- package/dist/ui/services.d.ts +33 -0
- package/dist/ui/services.js +67 -0
- package/dist/ui/services.js.map +1 -0
- package/dist/ui/start.d.ts +1 -0
- package/dist/ui/start.js +16 -0
- package/dist/ui/start.js.map +1 -0
- package/dist/ui/theme.d.ts +6 -0
- package/dist/ui/theme.js +26 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ImapFlow } from "imapflow";
|
|
2
|
+
import { createTransport } from "nodemailer";
|
|
3
|
+
import { HttpJson } from "../connectors/http.js";
|
|
4
|
+
const TIMEOUT_MS = 12_000;
|
|
5
|
+
export async function probeSlack(config, fetchImpl) {
|
|
6
|
+
try {
|
|
7
|
+
const api = new HttpJson({
|
|
8
|
+
connector: "slack",
|
|
9
|
+
baseUrl: "https://slack.com/api",
|
|
10
|
+
headers: { authorization: `Bearer ${config.botToken}` },
|
|
11
|
+
fetchImpl,
|
|
12
|
+
timeoutMs: TIMEOUT_MS,
|
|
13
|
+
maxRetries: 0,
|
|
14
|
+
});
|
|
15
|
+
const auth = await api.post("/auth.test", {});
|
|
16
|
+
if (!auth.ok)
|
|
17
|
+
return { ok: false, detail: auth.error ?? "auth.test failed" };
|
|
18
|
+
let socket = "events off (no SLACK_APP_TOKEN)";
|
|
19
|
+
if (config.appToken) {
|
|
20
|
+
const appApi = new HttpJson({
|
|
21
|
+
connector: "slack",
|
|
22
|
+
baseUrl: "https://slack.com/api",
|
|
23
|
+
headers: { authorization: `Bearer ${config.appToken}` },
|
|
24
|
+
fetchImpl,
|
|
25
|
+
timeoutMs: TIMEOUT_MS,
|
|
26
|
+
maxRetries: 0,
|
|
27
|
+
});
|
|
28
|
+
const open = await appApi.post("/apps.connections.open", {});
|
|
29
|
+
socket = open.ok ? "socket mode ready" : `socket mode failed: ${open.error ?? "?"}`;
|
|
30
|
+
if (!open.ok)
|
|
31
|
+
return { ok: false, detail: `bot ok (${auth.team}) but ${socket}` };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true, detail: `${auth.user} @ ${auth.team} · ${socket}` };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return { ok: false, detail: err.message };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function probeNotion(config, fetchImpl) {
|
|
40
|
+
try {
|
|
41
|
+
const api = new HttpJson({
|
|
42
|
+
connector: "notion",
|
|
43
|
+
baseUrl: "https://api.notion.com/v1",
|
|
44
|
+
headers: { authorization: `Bearer ${config.token}`, "notion-version": "2022-06-28" },
|
|
45
|
+
fetchImpl,
|
|
46
|
+
timeoutMs: TIMEOUT_MS,
|
|
47
|
+
maxRetries: 0,
|
|
48
|
+
});
|
|
49
|
+
const me = await api.get("/users/me");
|
|
50
|
+
return { ok: true, detail: `integration "${me.name ?? "?"}"${me.bot?.workspace_name ? ` in ${me.bot.workspace_name}` : ""}` };
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
return { ok: false, detail: err.message };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function probeGitHub(config, fetchImpl) {
|
|
57
|
+
try {
|
|
58
|
+
const api = new HttpJson({
|
|
59
|
+
connector: "github",
|
|
60
|
+
baseUrl: "https://api.github.com",
|
|
61
|
+
headers: {
|
|
62
|
+
authorization: `Bearer ${config.token}`,
|
|
63
|
+
accept: "application/vnd.github+json",
|
|
64
|
+
"x-github-api-version": "2022-11-28",
|
|
65
|
+
"user-agent": "exempclaw",
|
|
66
|
+
},
|
|
67
|
+
fetchImpl,
|
|
68
|
+
timeoutMs: TIMEOUT_MS,
|
|
69
|
+
maxRetries: 0,
|
|
70
|
+
});
|
|
71
|
+
const me = await api.get("/user");
|
|
72
|
+
const repos = (config.repos ?? "").split(",").map((r) => r.trim()).filter(Boolean);
|
|
73
|
+
const failures = [];
|
|
74
|
+
for (const repo of repos) {
|
|
75
|
+
try {
|
|
76
|
+
await api.get(`/repos/${repo}`);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
failures.push(repo);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (failures.length > 0) {
|
|
83
|
+
return { ok: false, detail: `as ${me.login}, but cannot reach: ${failures.join(", ")}` };
|
|
84
|
+
}
|
|
85
|
+
return { ok: true, detail: `as ${me.login}${repos.length ? ` · ${repos.length} repo(s) reachable` : ""}` };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return { ok: false, detail: err.message };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function probeEmail(config, transports = {}) {
|
|
92
|
+
const parts = [];
|
|
93
|
+
try {
|
|
94
|
+
if (config.imapHost) {
|
|
95
|
+
const imapFactory = transports.imapFactory ?? ((opts) => new ImapFlow(opts));
|
|
96
|
+
const client = imapFactory({
|
|
97
|
+
host: config.imapHost,
|
|
98
|
+
port: Number(config.imapPort ?? 993),
|
|
99
|
+
secure: Number(config.imapPort ?? 993) === 993,
|
|
100
|
+
auth: { user: config.user, pass: config.password },
|
|
101
|
+
logger: false,
|
|
102
|
+
connectionTimeout: TIMEOUT_MS,
|
|
103
|
+
});
|
|
104
|
+
await client.connect();
|
|
105
|
+
await client.logout().catch(() => undefined);
|
|
106
|
+
parts.push(`imap ${config.imapHost} ok`);
|
|
107
|
+
}
|
|
108
|
+
if (config.smtpHost) {
|
|
109
|
+
const smtpFactory = transports.smtpFactory ?? createTransport;
|
|
110
|
+
const transport = smtpFactory({
|
|
111
|
+
host: config.smtpHost,
|
|
112
|
+
port: Number(config.smtpPort ?? 587),
|
|
113
|
+
secure: Number(config.smtpPort ?? 587) === 465,
|
|
114
|
+
auth: { user: config.user, pass: config.password },
|
|
115
|
+
connectionTimeout: TIMEOUT_MS,
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
await transport.verify();
|
|
119
|
+
parts.push(`smtp ${config.smtpHost} ok`);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
transport.close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (parts.length === 0)
|
|
126
|
+
return { ok: false, detail: "no IMAP or SMTP host configured" };
|
|
127
|
+
return { ok: true, detail: parts.join(" · ") };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
return { ok: false, detail: [...parts, err.message].join(" · ") };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** Probe per connector id; consumed by `doctor`. */
|
|
134
|
+
export const PROBES = {
|
|
135
|
+
slack: (config) => probeSlack(config),
|
|
136
|
+
notion: (config) => probeNotion(config),
|
|
137
|
+
github: (config) => probeGitHub(config),
|
|
138
|
+
email: (config) => probeEmail(config),
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/cli/probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type UsageTotals } from "../core/usage.js";
|
|
2
|
+
export declare const dim: (text: string) => string;
|
|
3
|
+
export declare const bold: (text: string) => string;
|
|
4
|
+
export declare const green: (text: string) => string;
|
|
5
|
+
export declare const red: (text: string) => string;
|
|
6
|
+
export declare const yellow: (text: string) => string;
|
|
7
|
+
export declare const cyan: (text: string) => string;
|
|
8
|
+
export declare const ok: (text: string) => string;
|
|
9
|
+
export declare const fail: (text: string) => string;
|
|
10
|
+
export declare const warn: (text: string) => string;
|
|
11
|
+
/** Renders rows as a left-aligned table with two-space gutters. */
|
|
12
|
+
export declare function table(rows: string[][]): string;
|
|
13
|
+
export declare function formatTokens(n: number): string;
|
|
14
|
+
/** One-line run summary shown after chat/run completes. */
|
|
15
|
+
export declare function usageLine(usage: UsageTotals, costUsd: number | null, iterations: number): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { formatUsd } from "../core/usage.js";
|
|
2
|
+
/** Minimal ANSI helpers for the CLI. Colors disabled when not a TTY. */
|
|
3
|
+
const useColor = process.stdout.isTTY === true && process.env.NO_COLOR === undefined;
|
|
4
|
+
function wrap(code) {
|
|
5
|
+
return (text) => (useColor ? `\x1b[${code}m${text}\x1b[0m` : text);
|
|
6
|
+
}
|
|
7
|
+
export const dim = wrap("2");
|
|
8
|
+
export const bold = wrap("1");
|
|
9
|
+
export const green = wrap("32");
|
|
10
|
+
export const red = wrap("31");
|
|
11
|
+
export const yellow = wrap("33");
|
|
12
|
+
export const cyan = wrap("36");
|
|
13
|
+
export const ok = (text) => `${green("✓")} ${text}`;
|
|
14
|
+
export const fail = (text) => `${red("✗")} ${text}`;
|
|
15
|
+
export const warn = (text) => `${yellow("!")} ${text}`;
|
|
16
|
+
/** Renders rows as a left-aligned table with two-space gutters. */
|
|
17
|
+
export function table(rows) {
|
|
18
|
+
if (rows.length === 0)
|
|
19
|
+
return "";
|
|
20
|
+
const widths = [];
|
|
21
|
+
for (const row of rows) {
|
|
22
|
+
row.forEach((cell, i) => {
|
|
23
|
+
widths[i] = Math.max(widths[i] ?? 0, stripAnsi(cell).length);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return rows
|
|
27
|
+
.map((row) => row
|
|
28
|
+
.map((cell, i) => cell + " ".repeat((widths[i] ?? 0) - stripAnsi(cell).length))
|
|
29
|
+
.join(" ")
|
|
30
|
+
.trimEnd())
|
|
31
|
+
.join("\n");
|
|
32
|
+
}
|
|
33
|
+
function stripAnsi(text) {
|
|
34
|
+
// eslint-disable-next-line no-control-regex
|
|
35
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
36
|
+
}
|
|
37
|
+
export function formatTokens(n) {
|
|
38
|
+
if (n >= 1_000_000)
|
|
39
|
+
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
40
|
+
if (n >= 10_000)
|
|
41
|
+
return `${Math.round(n / 1000)}k`;
|
|
42
|
+
return String(n);
|
|
43
|
+
}
|
|
44
|
+
/** One-line run summary shown after chat/run completes. */
|
|
45
|
+
export function usageLine(usage, costUsd, iterations) {
|
|
46
|
+
const tokens = `${formatTokens(usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens)} in / ${formatTokens(usage.outputTokens)} out`;
|
|
47
|
+
const cache = usage.cacheReadTokens > 0 ? ` · ${formatTokens(usage.cacheReadTokens)} cached` : "";
|
|
48
|
+
return dim(`↳ ${iterations} turn${iterations === 1 ? "" : "s"} · ${tokens}${cache} · ${formatUsd(costUsd)}`);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/cli/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export type Paint = "amber" | "red" | "green" | "dim" | "plain";
|
|
2
|
+
/** A frame-based animation. All frames should render at the same width. */
|
|
3
|
+
export interface Anim {
|
|
4
|
+
frames: string[];
|
|
5
|
+
intervalMs: number;
|
|
6
|
+
color?: Paint;
|
|
7
|
+
}
|
|
8
|
+
export declare function frameAt(anim: Anim, elapsedMs: number): string;
|
|
9
|
+
export declare const ANIMS: {
|
|
10
|
+
/** The mascot: a claw, pinching. */
|
|
11
|
+
claw: Anim;
|
|
12
|
+
/** Classic braille spinner — model is reasoning. */
|
|
13
|
+
thinking: Anim;
|
|
14
|
+
/** A lens sweeping across slots. */
|
|
15
|
+
searching: Anim;
|
|
16
|
+
/** Pages turning. */
|
|
17
|
+
reading: Anim;
|
|
18
|
+
/** A cursor laying down ink. */
|
|
19
|
+
writing: Anim;
|
|
20
|
+
/** An envelope leaving a dot trail. */
|
|
21
|
+
sending: Anim;
|
|
22
|
+
/** Patient arc. */
|
|
23
|
+
waiting: Anim;
|
|
24
|
+
/** Sparkle burst for wins. */
|
|
25
|
+
celebrating: Anim;
|
|
26
|
+
/** Attention blink. */
|
|
27
|
+
alert: Anim;
|
|
28
|
+
/** Generic tool gear-tick. */
|
|
29
|
+
tool: Anim;
|
|
30
|
+
};
|
|
31
|
+
export type AnimName = keyof typeof ANIMS;
|
|
32
|
+
export declare function toolIcon(name: string): string;
|
|
33
|
+
export interface StageWriter {
|
|
34
|
+
write(text: string): unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface StageOptions {
|
|
37
|
+
out?: StageWriter;
|
|
38
|
+
/** Force TTY behavior on/off; defaults to stdout.isTTY && !EXEMPCLAW_NO_ANIM. */
|
|
39
|
+
tty?: boolean;
|
|
40
|
+
intervalMs?: number;
|
|
41
|
+
now?: () => number;
|
|
42
|
+
}
|
|
43
|
+
export declare class Stage {
|
|
44
|
+
private readonly out;
|
|
45
|
+
private readonly tty;
|
|
46
|
+
private readonly intervalMs;
|
|
47
|
+
private readonly now;
|
|
48
|
+
private rows;
|
|
49
|
+
private renderedLines;
|
|
50
|
+
private timer?;
|
|
51
|
+
private suspended;
|
|
52
|
+
private cursorHidden;
|
|
53
|
+
constructor(opts?: StageOptions);
|
|
54
|
+
get isAnimated(): boolean;
|
|
55
|
+
/** Adds (or replaces) an animated row. */
|
|
56
|
+
addRow(id: string, spec: {
|
|
57
|
+
label: string;
|
|
58
|
+
anim?: AnimName | Anim;
|
|
59
|
+
icon?: string;
|
|
60
|
+
color?: Paint;
|
|
61
|
+
hint?: string;
|
|
62
|
+
}): void;
|
|
63
|
+
updateRow(id: string, patch: {
|
|
64
|
+
label?: string;
|
|
65
|
+
hint?: string;
|
|
66
|
+
}): void;
|
|
67
|
+
/** Removes a row and prints its permanent settled line. */
|
|
68
|
+
settleRow(id: string, opts?: {
|
|
69
|
+
mark?: string;
|
|
70
|
+
color?: Paint;
|
|
71
|
+
label?: string;
|
|
72
|
+
suffix?: string;
|
|
73
|
+
}): void;
|
|
74
|
+
/** Removes a row without printing anything. */
|
|
75
|
+
removeRow(id: string): void;
|
|
76
|
+
/** Writes ordinary output above the live region. */
|
|
77
|
+
print(text: string): void;
|
|
78
|
+
/** Streaming-friendly raw write (no trailing newline added). */
|
|
79
|
+
write(text: string): void;
|
|
80
|
+
/** Temporarily clears the live region (e.g. while a prompt owns the line). */
|
|
81
|
+
suspend(): void;
|
|
82
|
+
resume(): void;
|
|
83
|
+
/** Clears everything without settling — end of a run or shutdown. */
|
|
84
|
+
stopAll(): void;
|
|
85
|
+
private takeRow;
|
|
86
|
+
private elapsedText;
|
|
87
|
+
private wake;
|
|
88
|
+
private stopTimer;
|
|
89
|
+
private erase;
|
|
90
|
+
private render;
|
|
91
|
+
private setCursor;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Plays the animated wordmark: the claw pinches while the letters sweep in.
|
|
95
|
+
* Resolves in ~700ms; prints a single static line on non-TTY terminals.
|
|
96
|
+
*/
|
|
97
|
+
export declare function playBanner(subtitle: string, opts?: StageOptions): Promise<void>;
|
|
98
|
+
/** Renders a fixed-width progress bar like ▰▰▰▱▱. */
|
|
99
|
+
export declare function progressBar(done: number, total: number, width?: number): string;
|
|
100
|
+
/** One-shot flash: a few quick frames, then a permanent line (TTY only). */
|
|
101
|
+
export declare function flashLine(text: string, opts?: StageOptions): Promise<void>;
|
package/dist/cli/tui.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
/**
|
|
3
|
+
* Dependency-free terminal animation engine.
|
|
4
|
+
*
|
|
5
|
+
* The Stage owns a "live region" at the bottom of the terminal: a set of
|
|
6
|
+
* animated rows (spinners, activity lines, progress bars) that are redrawn in
|
|
7
|
+
* place while ordinary output scrolls above them. Rows settle into permanent
|
|
8
|
+
* printed lines when their work finishes.
|
|
9
|
+
*
|
|
10
|
+
* Degrades gracefully: when stdout isn't a TTY (pipes, CI, tests) or
|
|
11
|
+
* EXEMPCLAW_NO_ANIM is set, rows print once as plain lines and settle as
|
|
12
|
+
* plain lines — no timers, no escape codes.
|
|
13
|
+
*/
|
|
14
|
+
const ESC = "\x1b[";
|
|
15
|
+
const HIDE_CURSOR = `${ESC}?25l`;
|
|
16
|
+
const SHOW_CURSOR = `${ESC}?25h`;
|
|
17
|
+
const CLEAR_LINE = `${ESC}2K`;
|
|
18
|
+
const AMBER = `${ESC}38;5;214m`;
|
|
19
|
+
const RED = `${ESC}31m`;
|
|
20
|
+
const GREEN = `${ESC}32m`;
|
|
21
|
+
const DIM = `${ESC}2m`;
|
|
22
|
+
const RESET = `${ESC}0m`;
|
|
23
|
+
function paint(color, text, enabled) {
|
|
24
|
+
if (!enabled || color === "plain")
|
|
25
|
+
return text;
|
|
26
|
+
const code = { amber: AMBER, red: RED, green: GREEN, dim: DIM }[color];
|
|
27
|
+
return `${code}${text}${RESET}`;
|
|
28
|
+
}
|
|
29
|
+
export function frameAt(anim, elapsedMs) {
|
|
30
|
+
const index = Math.floor(elapsedMs / anim.intervalMs) % anim.frames.length;
|
|
31
|
+
return anim.frames[index];
|
|
32
|
+
}
|
|
33
|
+
/* ── frame library ─────────────────────────────────────────────────── */
|
|
34
|
+
export const ANIMS = {
|
|
35
|
+
/** The mascot: a claw, pinching. */
|
|
36
|
+
claw: { frames: ["(\\/)", "(\\|)", "(\\-)", "(\\|)"], intervalMs: 220, color: "amber" },
|
|
37
|
+
/** Classic braille spinner — model is reasoning. */
|
|
38
|
+
thinking: { frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], intervalMs: 90, color: "amber" },
|
|
39
|
+
/** A lens sweeping across slots. */
|
|
40
|
+
searching: {
|
|
41
|
+
frames: ["[●····]", "[·●···]", "[··●··]", "[···●·]", "[····●]", "[···●·]", "[··●··]", "[·●···]"],
|
|
42
|
+
intervalMs: 110,
|
|
43
|
+
color: "amber",
|
|
44
|
+
},
|
|
45
|
+
/** Pages turning. */
|
|
46
|
+
reading: { frames: ["◐", "◓", "◑", "◒"], intervalMs: 160, color: "amber" },
|
|
47
|
+
/** A cursor laying down ink. */
|
|
48
|
+
writing: { frames: ["▍", "▌", "▋", "▊", "▋", "▌"], intervalMs: 130, color: "amber" },
|
|
49
|
+
/** An envelope leaving a dot trail. */
|
|
50
|
+
sending: {
|
|
51
|
+
frames: ["✉····", "·✉···", "··✉··", "···✉·", "····✉"],
|
|
52
|
+
intervalMs: 140,
|
|
53
|
+
color: "amber",
|
|
54
|
+
},
|
|
55
|
+
/** Patient arc. */
|
|
56
|
+
waiting: { frames: ["◜", "◠", "◝", "◞", "◡", "◟"], intervalMs: 140, color: "dim" },
|
|
57
|
+
/** Sparkle burst for wins. */
|
|
58
|
+
celebrating: { frames: ["✦ · ˙", "˙ ✦ ·", "· ˙ ✦", "˙ · ✦", "✧ ˙ ·"], intervalMs: 120, color: "amber" },
|
|
59
|
+
/** Attention blink. */
|
|
60
|
+
alert: { frames: ["▲", "△", "▲", "△"], intervalMs: 260, color: "red" },
|
|
61
|
+
/** Generic tool gear-tick. */
|
|
62
|
+
tool: { frames: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"], intervalMs: 90, color: "dim" },
|
|
63
|
+
};
|
|
64
|
+
/** Static icons that prefix tool rows, keyed by tool-name prefix. */
|
|
65
|
+
const TOOL_ICONS = [
|
|
66
|
+
["email_", "✉"],
|
|
67
|
+
["slack_", "#"],
|
|
68
|
+
["github_", "⎇"],
|
|
69
|
+
["notion_", "▤"],
|
|
70
|
+
["remember", "◆"],
|
|
71
|
+
["recall", "◇"],
|
|
72
|
+
["display_status", "≋"],
|
|
73
|
+
["current_time", "◷"],
|
|
74
|
+
];
|
|
75
|
+
export function toolIcon(name) {
|
|
76
|
+
for (const [prefix, icon] of TOOL_ICONS) {
|
|
77
|
+
if (name.startsWith(prefix))
|
|
78
|
+
return icon;
|
|
79
|
+
}
|
|
80
|
+
return "⚙";
|
|
81
|
+
}
|
|
82
|
+
export class Stage {
|
|
83
|
+
out;
|
|
84
|
+
tty;
|
|
85
|
+
intervalMs;
|
|
86
|
+
now;
|
|
87
|
+
rows = [];
|
|
88
|
+
renderedLines = 0;
|
|
89
|
+
timer;
|
|
90
|
+
suspended = false;
|
|
91
|
+
cursorHidden = false;
|
|
92
|
+
constructor(opts = {}) {
|
|
93
|
+
this.out = opts.out ?? process.stdout;
|
|
94
|
+
this.tty =
|
|
95
|
+
opts.tty ??
|
|
96
|
+
(process.stdout.isTTY === true && !process.env.EXEMPCLAW_NO_ANIM && !process.env.NO_COLOR);
|
|
97
|
+
this.intervalMs = opts.intervalMs ?? 80;
|
|
98
|
+
this.now = opts.now ?? (() => performance.now());
|
|
99
|
+
}
|
|
100
|
+
get isAnimated() {
|
|
101
|
+
return this.tty;
|
|
102
|
+
}
|
|
103
|
+
/** Adds (or replaces) an animated row. */
|
|
104
|
+
addRow(id, spec) {
|
|
105
|
+
const anim = typeof spec.anim === "object" ? spec.anim : ANIMS[spec.anim ?? "tool"];
|
|
106
|
+
const row = {
|
|
107
|
+
id,
|
|
108
|
+
icon: spec.icon,
|
|
109
|
+
label: spec.label,
|
|
110
|
+
anim,
|
|
111
|
+
color: spec.color ?? anim.color ?? "plain",
|
|
112
|
+
startedAt: this.now(),
|
|
113
|
+
hint: spec.hint,
|
|
114
|
+
};
|
|
115
|
+
const existing = this.rows.findIndex((r) => r.id === id);
|
|
116
|
+
if (existing >= 0) {
|
|
117
|
+
row.startedAt = this.rows[existing].startedAt;
|
|
118
|
+
this.rows[existing] = row;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
this.rows.push(row);
|
|
122
|
+
if (!this.tty) {
|
|
123
|
+
this.out.write(`${row.icon ? `${row.icon} ` : ""}${row.label}…\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
this.wake();
|
|
127
|
+
}
|
|
128
|
+
updateRow(id, patch) {
|
|
129
|
+
const row = this.rows.find((r) => r.id === id);
|
|
130
|
+
if (!row)
|
|
131
|
+
return;
|
|
132
|
+
if (patch.label !== undefined)
|
|
133
|
+
row.label = patch.label;
|
|
134
|
+
if (patch.hint !== undefined)
|
|
135
|
+
row.hint = patch.hint;
|
|
136
|
+
this.wake();
|
|
137
|
+
}
|
|
138
|
+
/** Removes a row and prints its permanent settled line. */
|
|
139
|
+
settleRow(id, opts = {}) {
|
|
140
|
+
const row = this.takeRow(id);
|
|
141
|
+
if (!row)
|
|
142
|
+
return;
|
|
143
|
+
const mark = opts.mark ?? "✓";
|
|
144
|
+
const color = opts.color ?? "green";
|
|
145
|
+
const label = opts.label ?? row.label;
|
|
146
|
+
const elapsed = this.elapsedText(row);
|
|
147
|
+
const line = `${paint(color, mark, this.tty)} ${row.icon ? `${row.icon} ` : ""}${label}${opts.suffix ? ` ${opts.suffix}` : ""}${elapsed}`;
|
|
148
|
+
this.print(`${line}\n`);
|
|
149
|
+
}
|
|
150
|
+
/** Removes a row without printing anything. */
|
|
151
|
+
removeRow(id) {
|
|
152
|
+
this.takeRow(id);
|
|
153
|
+
}
|
|
154
|
+
/** Writes ordinary output above the live region. */
|
|
155
|
+
print(text) {
|
|
156
|
+
if (!this.tty) {
|
|
157
|
+
this.out.write(text);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.erase();
|
|
161
|
+
this.out.write(text);
|
|
162
|
+
this.render();
|
|
163
|
+
}
|
|
164
|
+
/** Streaming-friendly raw write (no trailing newline added). */
|
|
165
|
+
write(text) {
|
|
166
|
+
this.print(text);
|
|
167
|
+
}
|
|
168
|
+
/** Temporarily clears the live region (e.g. while a prompt owns the line). */
|
|
169
|
+
suspend() {
|
|
170
|
+
if (this.suspended)
|
|
171
|
+
return;
|
|
172
|
+
this.suspended = true;
|
|
173
|
+
if (this.tty) {
|
|
174
|
+
this.erase();
|
|
175
|
+
this.stopTimer();
|
|
176
|
+
this.setCursor(true);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
resume() {
|
|
180
|
+
if (!this.suspended)
|
|
181
|
+
return;
|
|
182
|
+
this.suspended = false;
|
|
183
|
+
this.wake();
|
|
184
|
+
}
|
|
185
|
+
/** Clears everything without settling — end of a run or shutdown. */
|
|
186
|
+
stopAll() {
|
|
187
|
+
this.rows = [];
|
|
188
|
+
if (this.tty)
|
|
189
|
+
this.erase();
|
|
190
|
+
this.stopTimer();
|
|
191
|
+
this.setCursor(true);
|
|
192
|
+
}
|
|
193
|
+
/* ── internals ── */
|
|
194
|
+
takeRow(id) {
|
|
195
|
+
const index = this.rows.findIndex((r) => r.id === id);
|
|
196
|
+
if (index < 0)
|
|
197
|
+
return undefined;
|
|
198
|
+
const [row] = this.rows.splice(index, 1);
|
|
199
|
+
if (this.tty) {
|
|
200
|
+
this.erase();
|
|
201
|
+
this.render();
|
|
202
|
+
}
|
|
203
|
+
if (this.rows.length === 0)
|
|
204
|
+
this.stopTimer();
|
|
205
|
+
return row;
|
|
206
|
+
}
|
|
207
|
+
elapsedText(row) {
|
|
208
|
+
const seconds = (this.now() - row.startedAt) / 1000;
|
|
209
|
+
if (seconds < 0.05)
|
|
210
|
+
return "";
|
|
211
|
+
return paint("dim", ` · ${seconds.toFixed(1)}s`, this.tty);
|
|
212
|
+
}
|
|
213
|
+
wake() {
|
|
214
|
+
if (!this.tty || this.suspended)
|
|
215
|
+
return;
|
|
216
|
+
this.setCursor(false);
|
|
217
|
+
this.render();
|
|
218
|
+
if (!this.timer && this.rows.length > 0) {
|
|
219
|
+
this.timer = setInterval(() => this.render(), this.intervalMs);
|
|
220
|
+
this.timer.unref?.();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
stopTimer() {
|
|
224
|
+
if (this.timer) {
|
|
225
|
+
clearInterval(this.timer);
|
|
226
|
+
this.timer = undefined;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
erase() {
|
|
230
|
+
if (this.renderedLines === 0)
|
|
231
|
+
return;
|
|
232
|
+
let seq = "";
|
|
233
|
+
for (let i = 0; i < this.renderedLines; i++) {
|
|
234
|
+
seq += `${ESC}1A${CLEAR_LINE}`;
|
|
235
|
+
}
|
|
236
|
+
seq += "\r";
|
|
237
|
+
this.out.write(seq);
|
|
238
|
+
this.renderedLines = 0;
|
|
239
|
+
}
|
|
240
|
+
render() {
|
|
241
|
+
if (!this.tty || this.suspended)
|
|
242
|
+
return;
|
|
243
|
+
this.erase();
|
|
244
|
+
if (this.rows.length === 0) {
|
|
245
|
+
this.setCursor(true);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
let block = "";
|
|
249
|
+
for (const row of this.rows) {
|
|
250
|
+
const frame = paint(row.color, frameAt(row.anim, this.now() - row.startedAt), this.tty);
|
|
251
|
+
const icon = row.icon ? `${row.icon} ` : "";
|
|
252
|
+
const hint = row.hint ? paint("dim", ` ${row.hint}`, this.tty) : "";
|
|
253
|
+
block += `${frame} ${icon}${row.label}${hint}${this.elapsedText(row)}\n`;
|
|
254
|
+
}
|
|
255
|
+
this.out.write(block);
|
|
256
|
+
this.renderedLines = this.rows.length;
|
|
257
|
+
}
|
|
258
|
+
setCursor(visible) {
|
|
259
|
+
if (!this.tty)
|
|
260
|
+
return;
|
|
261
|
+
if (visible && this.cursorHidden) {
|
|
262
|
+
this.out.write(SHOW_CURSOR);
|
|
263
|
+
this.cursorHidden = false;
|
|
264
|
+
}
|
|
265
|
+
else if (!visible && !this.cursorHidden) {
|
|
266
|
+
this.out.write(HIDE_CURSOR);
|
|
267
|
+
this.cursorHidden = true;
|
|
268
|
+
ensureCursorRestoredOnExit();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
let exitHookInstalled = false;
|
|
273
|
+
function ensureCursorRestoredOnExit() {
|
|
274
|
+
if (exitHookInstalled)
|
|
275
|
+
return;
|
|
276
|
+
exitHookInstalled = true;
|
|
277
|
+
process.on("exit", () => {
|
|
278
|
+
if (process.stdout.isTTY)
|
|
279
|
+
process.stdout.write(SHOW_CURSOR);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/* ── banner ────────────────────────────────────────────────────────── */
|
|
283
|
+
/**
|
|
284
|
+
* Plays the animated wordmark: the claw pinches while the letters sweep in.
|
|
285
|
+
* Resolves in ~700ms; prints a single static line on non-TTY terminals.
|
|
286
|
+
*/
|
|
287
|
+
export async function playBanner(subtitle, opts = {}) {
|
|
288
|
+
const out = opts.out ?? process.stdout;
|
|
289
|
+
const tty = opts.tty ?? (process.stdout.isTTY === true && !process.env.EXEMPCLAW_NO_ANIM && !process.env.NO_COLOR);
|
|
290
|
+
const word = "E X E M P C L A W";
|
|
291
|
+
if (!tty) {
|
|
292
|
+
out.write(`(\\/) ${word} — ${subtitle}\n`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const claw = ANIMS.claw.frames;
|
|
296
|
+
const steps = 10;
|
|
297
|
+
out.write(HIDE_CURSOR);
|
|
298
|
+
for (let i = 0; i <= steps; i++) {
|
|
299
|
+
const visible = Math.round((word.length * i) / steps);
|
|
300
|
+
const frame = claw[i % claw.length];
|
|
301
|
+
const text = word.slice(0, visible);
|
|
302
|
+
const ghost = " ".repeat(word.length - visible);
|
|
303
|
+
out.write(`\r${CLEAR_LINE}${AMBER}${frame}${RESET} ${AMBER}${text}${RESET}${ghost}`);
|
|
304
|
+
await sleepMs(55);
|
|
305
|
+
}
|
|
306
|
+
out.write(`\r${CLEAR_LINE}${AMBER}(\\/)${RESET} ${AMBER}${word}${RESET}\n`);
|
|
307
|
+
out.write(`${DIM} ${subtitle}${RESET}\n`);
|
|
308
|
+
out.write(SHOW_CURSOR);
|
|
309
|
+
}
|
|
310
|
+
/** Renders a fixed-width progress bar like ▰▰▰▱▱. */
|
|
311
|
+
export function progressBar(done, total, width = 14) {
|
|
312
|
+
if (total <= 0)
|
|
313
|
+
return "▱".repeat(width);
|
|
314
|
+
const filled = Math.max(0, Math.min(width, Math.round((done / total) * width)));
|
|
315
|
+
return "▰".repeat(filled) + "▱".repeat(width - filled);
|
|
316
|
+
}
|
|
317
|
+
/** One-shot flash: a few quick frames, then a permanent line (TTY only). */
|
|
318
|
+
export async function flashLine(text, opts = {}) {
|
|
319
|
+
const out = opts.out ?? process.stdout;
|
|
320
|
+
const tty = opts.tty ?? (process.stdout.isTTY === true && !process.env.EXEMPCLAW_NO_ANIM && !process.env.NO_COLOR);
|
|
321
|
+
if (!tty) {
|
|
322
|
+
out.write(`${text}\n`);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
for (const frame of ["⚡", "✦", "⚡"]) {
|
|
326
|
+
out.write(`\r${CLEAR_LINE}${AMBER}${frame}${RESET} ${text}`);
|
|
327
|
+
await sleepMs(70);
|
|
328
|
+
}
|
|
329
|
+
out.write(`\r${CLEAR_LINE}${AMBER}⚡${RESET} ${text}\n`);
|
|
330
|
+
}
|
|
331
|
+
function sleepMs(ms) {
|
|
332
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=tui.js.map
|