-
Notifications
You must be signed in to change notification settings - Fork 506
Expand file tree
/
Copy pathsettings.ts
More file actions
119 lines (103 loc) · 4.44 KB
/
Copy pathsettings.ts
File metadata and controls
119 lines (103 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import fs from 'fs';
import os from 'os';
import path from 'path';
import { Hono } from 'hono';
import { Settings } from '@tinyclaw/core';
import { SETTINGS_FILE, TINYCLAW_HOME, getSettings, ensureAgentDirectory, copyDirSync, SCRIPT_DIR } from '@tinyclaw/core';
import { log } from '@tinyclaw/core';
/** Read, mutate, and persist settings.json atomically. */
export function mutateSettings(fn: (settings: Settings) => void): Settings {
const settings = getSettings();
fn(settings);
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
return settings;
}
const app = new Hono();
function expandHomePath(input?: string): string | undefined {
if (!input) return input;
const home = process.env.HOME || os.homedir();
if (!home) return input;
if (input === '~') return home;
if (input.startsWith('~/')) return path.join(home, input.slice(2));
if (input === '$HOME') return home;
if (input.startsWith('$HOME/')) return path.join(home, input.slice(6));
return input;
}
// GET /api/settings
app.get('/api/settings', (c) => {
return c.json(getSettings());
});
// PUT /api/settings
app.put('/api/settings', async (c) => {
const body = await c.req.json();
const current = getSettings();
const merged = { ...current, ...body } as Settings;
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2) + '\n');
log('INFO', '[API] Settings updated');
return c.json({ ok: true, settings: merged });
});
// POST /api/setup — run initial setup (write settings + create directories)
// Requires ?force=true if settings.json already exists with agents configured,
// to prevent agents from accidentally wiping a live configuration.
app.post('/api/setup', async (c) => {
const force = c.req.query('force') === 'true';
const settings = (await c.req.json()) as Settings;
// Guard: refuse to overwrite an existing configured installation unless forced
if (!force && fs.existsSync(SETTINGS_FILE)) {
const existing = (() => { try { return JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8')); } catch { return null; } })();
if (existing?.agents && Object.keys(existing.agents).length > 0) {
log('WARN', '[API] Setup blocked: settings.json already has agents configured. Use ?force=true to overwrite.');
return c.json({ ok: false, error: 'Settings already configured. Pass ?force=true to overwrite.' }, 409);
}
}
if (settings.workspace?.path) {
settings.workspace.path = expandHomePath(settings.workspace.path);
}
if (settings.agents) {
for (const agent of Object.values(settings.agents)) {
if (agent.working_directory) {
agent.working_directory = expandHomePath(agent.working_directory) || agent.working_directory;
}
}
}
// Back up existing settings before overwriting
fs.mkdirSync(path.dirname(SETTINGS_FILE), { recursive: true });
if (fs.existsSync(SETTINGS_FILE)) {
const backupPath = `${SETTINGS_FILE}.bak`;
fs.copyFileSync(SETTINGS_FILE, backupPath);
log('INFO', `[API] Setup: backed up existing settings to ${backupPath}`);
}
// Write settings.json
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
log('INFO', '[API] Setup: settings.json written');
// Create TINYCLAW_HOME directories
fs.mkdirSync(path.join(TINYCLAW_HOME, 'logs'), { recursive: true });
fs.mkdirSync(path.join(TINYCLAW_HOME, 'files'), { recursive: true });
// Copy template files into TINYCLAW_HOME
const templateItems = ['.claude', 'heartbeat.md', 'AGENTS.md'];
for (const item of templateItems) {
const srcPath = path.join(SCRIPT_DIR, item);
const destPath = path.join(TINYCLAW_HOME, item);
if (fs.existsSync(srcPath)) {
if (fs.statSync(srcPath).isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
// Create workspace directory
const workspacePath = settings.workspace?.path;
if (workspacePath) {
fs.mkdirSync(workspacePath, { recursive: true });
}
// Create agent directories
if (settings.agents) {
for (const agent of Object.values(settings.agents)) {
ensureAgentDirectory(agent.working_directory);
}
}
log('INFO', '[API] Setup complete');
return c.json({ ok: true, settings });
});
export default app;