chore: format + lint
parent
8a01dc7f4c
commit
a67f4db5e2
|
|
@ -10,16 +10,16 @@ export type ClaudeOutputFormat = "text" | "json" | "stream-json";
|
||||||
export type SessionScope = "per-sender" | "global";
|
export type SessionScope = "per-sender" | "global";
|
||||||
|
|
||||||
export type SessionConfig = {
|
export type SessionConfig = {
|
||||||
scope?: SessionScope;
|
scope?: SessionScope;
|
||||||
resetTriggers?: string[];
|
resetTriggers?: string[];
|
||||||
idleMinutes?: number;
|
idleMinutes?: number;
|
||||||
store?: string;
|
store?: string;
|
||||||
sessionArgNew?: string[];
|
sessionArgNew?: string[];
|
||||||
sessionArgResume?: string[];
|
sessionArgResume?: string[];
|
||||||
sessionArgBeforeBody?: boolean;
|
sessionArgBeforeBody?: boolean;
|
||||||
sendSystemOnce?: boolean;
|
sendSystemOnce?: boolean;
|
||||||
sessionIntro?: string;
|
sessionIntro?: string;
|
||||||
typingIntervalSeconds?: number;
|
typingIntervalSeconds?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoggingConfig = {
|
export type LoggingConfig = {
|
||||||
|
|
@ -28,29 +28,29 @@ export type LoggingConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WarelayConfig = {
|
export type WarelayConfig = {
|
||||||
logging?: LoggingConfig;
|
logging?: LoggingConfig;
|
||||||
inbound?: {
|
inbound?: {
|
||||||
allowFrom?: string[]; // E.164 numbers allowed to trigger auto-reply (without whatsapp:)
|
allowFrom?: string[]; // E.164 numbers allowed to trigger auto-reply (without whatsapp:)
|
||||||
transcribeAudio?: {
|
transcribeAudio?: {
|
||||||
// Optional CLI to turn inbound audio into text; templated args, must output transcript to stdout.
|
// Optional CLI to turn inbound audio into text; templated args, must output transcript to stdout.
|
||||||
command: string[];
|
command: string[];
|
||||||
timeoutSeconds?: number;
|
timeoutSeconds?: number;
|
||||||
};
|
};
|
||||||
reply?: {
|
reply?: {
|
||||||
mode: ReplyMode;
|
mode: ReplyMode;
|
||||||
text?: string; // for mode=text, can contain {{Body}}
|
text?: string; // for mode=text, can contain {{Body}}
|
||||||
command?: string[]; // for mode=command, argv with templates
|
command?: string[]; // for mode=command, argv with templates
|
||||||
cwd?: string; // working directory for command execution
|
cwd?: string; // working directory for command execution
|
||||||
template?: string; // prepend template string when building command/prompt
|
template?: string; // prepend template string when building command/prompt
|
||||||
timeoutSeconds?: number; // optional command timeout; defaults to 600s
|
timeoutSeconds?: number; // optional command timeout; defaults to 600s
|
||||||
bodyPrefix?: string; // optional string prepended to Body before templating
|
bodyPrefix?: string; // optional string prepended to Body before templating
|
||||||
mediaUrl?: string; // optional media attachment (path or URL)
|
mediaUrl?: string; // optional media attachment (path or URL)
|
||||||
session?: SessionConfig;
|
session?: SessionConfig;
|
||||||
claudeOutputFormat?: ClaudeOutputFormat; // when command starts with `claude`, force an output format
|
claudeOutputFormat?: ClaudeOutputFormat; // when command starts with `claude`, force an output format
|
||||||
mediaMaxMb?: number; // optional cap for outbound media (default 5MB)
|
mediaMaxMb?: number; // optional cap for outbound media (default 5MB)
|
||||||
typingIntervalSeconds?: number; // how often to refresh typing indicator while command runs
|
typingIntervalSeconds?: number; // how often to refresh typing indicator while command runs
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CONFIG_PATH = path.join(os.homedir(), ".warelay", "warelay.json");
|
export const CONFIG_PATH = path.join(os.homedir(), ".warelay", "warelay.json");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import net from "node:net";
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import net from "node:net";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { MessageInstance } from "twilio/lib/rest/api/v2010/account/message.js";
|
import type { MessageInstance } from "twilio/lib/rest/api/v2010/account/message.js";
|
||||||
|
|
@ -144,11 +144,11 @@ describe("config and templating", () => {
|
||||||
killed: false,
|
killed: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await index.getReplyFromConfig(
|
const result = await index.getReplyFromConfig(
|
||||||
{
|
{
|
||||||
Body: "<media:audio>",
|
Body: "<media:audio>",
|
||||||
From: "+1",
|
From: "+1",
|
||||||
To: "+2",
|
To: "+2",
|
||||||
MediaPath: "/tmp/voice.ogg",
|
MediaPath: "/tmp/voice.ogg",
|
||||||
MediaType: "audio/ogg",
|
MediaType: "audio/ogg",
|
||||||
},
|
},
|
||||||
|
|
@ -157,15 +157,15 @@ describe("config and templating", () => {
|
||||||
commandRunner,
|
commandRunner,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(runExec).toHaveBeenCalled();
|
expect(runExec).toHaveBeenCalled();
|
||||||
expect(commandRunner).toHaveBeenCalled();
|
expect(commandRunner).toHaveBeenCalled();
|
||||||
const argv = commandRunner.mock.calls[0][0];
|
const argv = commandRunner.mock.calls[0][0];
|
||||||
const prompt = argv[argv.length - 1] as string;
|
const prompt = argv[argv.length - 1] as string;
|
||||||
expect(prompt).toContain("/tmp/voice.ogg");
|
expect(prompt).toContain("/tmp/voice.ogg");
|
||||||
expect(prompt).toContain("Transcript:");
|
expect(prompt).toContain("Transcript:");
|
||||||
expect(prompt).toContain("voice transcript");
|
expect(prompt).toContain("voice transcript");
|
||||||
expect(result?.text).toBe("ok");
|
expect(result?.text).toBe("ok");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getReplyFromConfig skips transcription when not configured", async () => {
|
it("getReplyFromConfig skips transcription when not configured", async () => {
|
||||||
const cfg = {
|
const cfg = {
|
||||||
|
|
@ -640,44 +640,44 @@ describe("config and templating", () => {
|
||||||
expect(onReplyStart.mock.calls.length).toBeGreaterThanOrEqual(3);
|
expect(onReplyStart.mock.calls.length).toBeGreaterThanOrEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses session typing interval override", async () => {
|
it("uses session typing interval override", async () => {
|
||||||
const onReplyStart = vi.fn();
|
const onReplyStart = vi.fn();
|
||||||
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockImplementation(
|
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockImplementation(
|
||||||
() =>
|
() =>
|
||||||
new Promise((resolve) =>
|
new Promise((resolve) =>
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() =>
|
() =>
|
||||||
resolve({
|
resolve({
|
||||||
stdout: "done\n",
|
stdout: "done\n",
|
||||||
stderr: "",
|
stderr: "",
|
||||||
code: 0,
|
code: 0,
|
||||||
signal: null,
|
signal: null,
|
||||||
killed: false,
|
killed: false,
|
||||||
}),
|
}),
|
||||||
120,
|
120,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const cfg = {
|
const cfg = {
|
||||||
inbound: {
|
inbound: {
|
||||||
reply: {
|
reply: {
|
||||||
mode: "command" as const,
|
mode: "command" as const,
|
||||||
command: ["echo", "{{Body}}"],
|
command: ["echo", "{{Body}}"],
|
||||||
session: { typingIntervalSeconds: 0.02 },
|
session: { typingIntervalSeconds: 0.02 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = index.getReplyFromConfig(
|
const promise = index.getReplyFromConfig(
|
||||||
{ Body: "hi", From: "+1", To: "+2" },
|
{ Body: "hi", From: "+1", To: "+2" },
|
||||||
{ onReplyStart },
|
{ onReplyStart },
|
||||||
cfg,
|
cfg,
|
||||||
runSpy,
|
runSpy,
|
||||||
);
|
);
|
||||||
await new Promise((r) => setTimeout(r, 200));
|
await new Promise((r) => setTimeout(r, 200));
|
||||||
await promise;
|
await promise;
|
||||||
expect(onReplyStart.mock.calls.length).toBeGreaterThanOrEqual(3);
|
expect(onReplyStart.mock.calls.length).toBeGreaterThanOrEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("injects Claude output format + print flag when configured", async () => {
|
it("injects Claude output format + print flag when configured", async () => {
|
||||||
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({
|
const runSpy = vi.spyOn(index, "runCommandWithTimeout").mockResolvedValue({
|
||||||
|
|
|
||||||
|
|
@ -501,42 +501,42 @@ describe("provider-web", () => {
|
||||||
fetchMock.mockRestore();
|
fetchMock.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(
|
it(
|
||||||
"compresses common formats to jpeg under the cap",
|
"compresses common formats to jpeg under the cap",
|
||||||
{ timeout: 15_000 },
|
{ timeout: 15_000 },
|
||||||
async () => {
|
async () => {
|
||||||
const formats = [
|
const formats = [
|
||||||
{
|
{
|
||||||
name: "png",
|
name: "png",
|
||||||
mime: "image/png",
|
mime: "image/png",
|
||||||
make: (buf: Buffer, opts: { width: number; height: number }) =>
|
make: (buf: Buffer, opts: { width: number; height: number }) =>
|
||||||
sharp(buf, {
|
sharp(buf, {
|
||||||
raw: { width: opts.width, height: opts.height, channels: 3 },
|
raw: { width: opts.width, height: opts.height, channels: 3 },
|
||||||
})
|
})
|
||||||
.png({ compressionLevel: 0 })
|
.png({ compressionLevel: 0 })
|
||||||
.toBuffer(),
|
.toBuffer(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "jpeg",
|
name: "jpeg",
|
||||||
mime: "image/jpeg",
|
mime: "image/jpeg",
|
||||||
make: (buf: Buffer, opts: { width: number; height: number }) =>
|
make: (buf: Buffer, opts: { width: number; height: number }) =>
|
||||||
sharp(buf, {
|
sharp(buf, {
|
||||||
raw: { width: opts.width, height: opts.height, channels: 3 },
|
raw: { width: opts.width, height: opts.height, channels: 3 },
|
||||||
})
|
})
|
||||||
.jpeg({ quality: 100, chromaSubsampling: "4:4:4" })
|
.jpeg({ quality: 100, chromaSubsampling: "4:4:4" })
|
||||||
.toBuffer(),
|
.toBuffer(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "webp",
|
name: "webp",
|
||||||
mime: "image/webp",
|
mime: "image/webp",
|
||||||
make: (buf: Buffer, opts: { width: number; height: number }) =>
|
make: (buf: Buffer, opts: { width: number; height: number }) =>
|
||||||
sharp(buf, {
|
sharp(buf, {
|
||||||
raw: { width: opts.width, height: opts.height, channels: 3 },
|
raw: { width: opts.width, height: opts.height, channels: 3 },
|
||||||
})
|
})
|
||||||
.webp({ quality: 100 })
|
.webp({ quality: 100 })
|
||||||
.toBuffer(),
|
.toBuffer(),
|
||||||
},
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
for (const fmt of formats) {
|
for (const fmt of formats) {
|
||||||
// Force a small cap to ensure compression is exercised for every format.
|
// Force a small cap to ensure compression is exercised for every format.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue