macroclaw 0.2.0 → 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/README.md +33 -27
- package/bin/macroclaw.js +3 -1
- package/package.json +4 -3
- package/src/claude.ts +1 -1
- package/src/cli.test.ts +293 -0
- package/src/cli.ts +186 -0
- package/src/index.ts +31 -41
- package/src/logger.test.ts +18 -7
- package/src/logger.ts +17 -7
- package/src/service.test.ts +590 -0
- package/src/service.ts +346 -0
- package/src/setup.test.ts +160 -3
- package/src/setup.ts +63 -8
package/src/index.ts
CHANGED
|
@@ -1,58 +1,48 @@
|
|
|
1
1
|
import { cpSync, existsSync, readdirSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { createInterface } from "node:readline";
|
|
4
3
|
import { App, type AppConfig } from "./app";
|
|
5
4
|
import { createLogger, initLogger } from "./logger";
|
|
6
|
-
import { applyEnvOverrides, loadSettings, printSettings
|
|
7
|
-
import { runSetupWizard } from "./setup";
|
|
5
|
+
import { applyEnvOverrides, loadSettings, printSettings } from "./settings";
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
const log = createLogger("index");
|
|
7
|
+
export async function start(): Promise<void> {
|
|
8
|
+
const log = createLogger("index");
|
|
11
9
|
|
|
12
|
-
const defaultDir = resolve(process.env.HOME || "~", ".macroclaw");
|
|
10
|
+
const defaultDir = resolve(process.env.HOME || "~", ".macroclaw");
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
const settings = loadSettings(defaultDir);
|
|
15
13
|
|
|
16
|
-
if (!settings) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
ask: (question: string): Promise<string> =>
|
|
21
|
-
new Promise((resolve) => rl.question(question, (answer) => resolve(answer.trim()))),
|
|
22
|
-
write: (msg: string) => process.stdout.write(msg),
|
|
23
|
-
};
|
|
24
|
-
settings = await runSetupWizard(io);
|
|
25
|
-
rl.close();
|
|
26
|
-
saveSettings(settings, defaultDir);
|
|
27
|
-
log.info("Settings saved");
|
|
28
|
-
}
|
|
14
|
+
if (!settings) {
|
|
15
|
+
log.error("No settings found. Run `macroclaw setup` first.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
29
18
|
|
|
30
|
-
const { settings: resolved, overrides } = applyEnvOverrides(settings);
|
|
31
|
-
settings = resolved;
|
|
19
|
+
const { settings: resolved, overrides } = applyEnvOverrides(settings);
|
|
32
20
|
|
|
33
|
-
|
|
21
|
+
await initLogger({ level: resolved.logLevel, pinoramaUrl: resolved.pinoramaUrl });
|
|
22
|
+
printSettings(resolved, overrides);
|
|
34
23
|
|
|
35
|
-
const workspace = resolve(
|
|
24
|
+
const workspace = resolve(resolved.workspace.replace(/^~/, process.env.HOME || "~"));
|
|
36
25
|
|
|
37
|
-
function initWorkspace(workspace: string) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
function initWorkspace(workspace: string) {
|
|
27
|
+
const templateDir = join(dirname(import.meta.dir), "workspace-template");
|
|
28
|
+
const exists = existsSync(workspace);
|
|
29
|
+
const empty = exists && readdirSync(workspace).length === 0;
|
|
41
30
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
if (!exists || empty) {
|
|
32
|
+
log.info({ workspace }, "Initializing workspace from template");
|
|
33
|
+
cpSync(templateDir, workspace, { recursive: true });
|
|
34
|
+
log.info("Workspace initialized");
|
|
35
|
+
}
|
|
46
36
|
}
|
|
47
|
-
}
|
|
48
37
|
|
|
49
|
-
initWorkspace(workspace);
|
|
38
|
+
initWorkspace(workspace);
|
|
50
39
|
|
|
51
|
-
const config: AppConfig = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
40
|
+
const config: AppConfig = {
|
|
41
|
+
botToken: resolved.botToken,
|
|
42
|
+
authorizedChatId: resolved.chatId,
|
|
43
|
+
workspace,
|
|
44
|
+
model: resolved.model,
|
|
45
|
+
};
|
|
57
46
|
|
|
58
|
-
new App(config).start();
|
|
47
|
+
new App(config).start();
|
|
48
|
+
}
|
package/src/logger.test.ts
CHANGED
|
@@ -17,17 +17,28 @@ describe("createLogger", () => {
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
describe("initLogger", () => {
|
|
20
|
-
it("
|
|
21
|
-
|
|
20
|
+
it("does nothing when called without opts", async () => {
|
|
21
|
+
mockPinoramaTransport.mockClear();
|
|
22
22
|
await initLogger();
|
|
23
|
-
expect(mockPinoramaTransport).
|
|
24
|
-
|
|
23
|
+
expect(mockPinoramaTransport).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("sets log level from opts", async () => {
|
|
27
|
+
const log = createLogger("opts-level");
|
|
28
|
+
await initLogger({ level: "warn" });
|
|
29
|
+
expect(log.level).toBe("warn");
|
|
30
|
+
await initLogger({ level: "info" }); // restore
|
|
25
31
|
});
|
|
26
32
|
|
|
27
|
-
it("
|
|
28
|
-
delete process.env.PINORAMA_URL;
|
|
33
|
+
it("adds pinorama transport from opts", async () => {
|
|
29
34
|
mockPinoramaTransport.mockClear();
|
|
30
|
-
await initLogger();
|
|
35
|
+
await initLogger({ pinoramaUrl: "http://example.com/pinorama" });
|
|
36
|
+
expect(mockPinoramaTransport).toHaveBeenCalledWith({ url: "http://example.com/pinorama" });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("does not add duplicate pinorama transport on second call", async () => {
|
|
40
|
+
mockPinoramaTransport.mockClear();
|
|
41
|
+
await initLogger({ pinoramaUrl: "http://example.com/pinorama" });
|
|
31
42
|
expect(mockPinoramaTransport).not.toHaveBeenCalled();
|
|
32
43
|
});
|
|
33
44
|
});
|
package/src/logger.ts
CHANGED
|
@@ -6,20 +6,30 @@ const prettyStream = pretty({
|
|
|
6
6
|
messageFormat: "[{module}] {msg}",
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const defaultLevel = (process.env.LOG_LEVEL || "info") as pino.Level;
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
// Streams accept all levels; logger.level is the sole gate
|
|
12
|
+
const streams: pino.StreamEntry[] = [{ level: "trace", stream: prettyStream }];
|
|
12
13
|
|
|
13
14
|
const logger = pino(
|
|
14
|
-
{ level:
|
|
15
|
+
{ level: defaultLevel },
|
|
15
16
|
pino.multistream(streams),
|
|
16
17
|
);
|
|
17
18
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
export interface LoggerOptions {
|
|
20
|
+
level?: pino.Level;
|
|
21
|
+
pinoramaUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let pinoramaAdded = false;
|
|
25
|
+
|
|
26
|
+
export async function initLogger(opts?: LoggerOptions): Promise<void> {
|
|
27
|
+
if (opts?.level) logger.level = opts.level;
|
|
28
|
+
|
|
29
|
+
if (opts?.pinoramaUrl && !pinoramaAdded) {
|
|
21
30
|
const { default: pinoramaTransport } = await import("pinorama-transport");
|
|
22
|
-
streams.push({ level, stream: pinoramaTransport({ url: pinoramaUrl }) });
|
|
31
|
+
streams.push({ level: "trace", stream: pinoramaTransport({ url: opts.pinoramaUrl }) });
|
|
32
|
+
pinoramaAdded = true;
|
|
23
33
|
}
|
|
24
34
|
}
|
|
25
35
|
|