gigaclaw 1.7.0 → 1.9.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/bin/bootstrap.mjs +452 -0
- package/bin/cli.js +65 -7
- package/bin/scaffold.mjs +188 -0
- package/drizzle/0005_knowledge_base_share_tokens.sql +22 -0
- package/drizzle/meta/_journal.json +7 -0
- package/lib/ai/model.js +1 -1
- package/lib/chat/components/app-sidebar.js +15 -1
- package/lib/chat/components/app-sidebar.jsx +19 -1
- package/lib/chat/components/icons.js +7 -0
- package/lib/chat/components/icons.jsx +9 -0
- package/lib/chat/components/index.js +2 -0
- package/lib/chat/components/knowledge-base-page.js +517 -0
- package/lib/chat/components/knowledge-base-page.jsx +654 -0
- package/lib/chat/components/share-dialog.js +403 -0
- package/lib/chat/components/share-dialog.jsx +542 -0
- package/lib/chat/knowledge-base-actions.js +335 -0
- package/lib/chat/share-actions.js +294 -0
- package/lib/db/audit-log.js +1 -1
- package/lib/db/schema.js +13 -0
- package/package.json +14 -2
- package/setup/lib/providers.mjs +2 -2
- package/setup/setup-hybrid.mjs +6 -5
- package/setup/setup-local.mjs +8 -7
- package/setup/setup-telegram.mjs +2 -1
- package/setup/setup.mjs +4 -3
- package/templates/app/knowledge-base/page.js +12 -0
- package/templates/app/share/[token]/page.js +200 -0
- package/templates/middleware.js +1 -1
- package/templates/next.config.mjs +2 -2
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GigaClaw One-Command Bootstrap — v1.9.0
|
|
4
|
+
*
|
|
5
|
+
* Invoked when user runs: npx gigaclaw@latest (no subcommand)
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. Detect environment (Node, Docker, Ollama)
|
|
9
|
+
* 2. Auto-create project directory (default: ./gigaclaw-app, or cwd if empty)
|
|
10
|
+
* 3. Run scaffolding silently
|
|
11
|
+
* 4. Reliable dependency installation (retry + cache clean + pnpm fallback)
|
|
12
|
+
* 5. Auto-run setup with smart defaults (hybrid mode, Claude Sonnet, auto routing)
|
|
13
|
+
* 6. Write .env automatically
|
|
14
|
+
* 7. Start dev server and auto-open browser
|
|
15
|
+
*
|
|
16
|
+
* Pass --interactive to enable the full interactive setup wizard instead of smart defaults.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { execSync, execFileSync, spawn } from 'child_process';
|
|
20
|
+
import fs from 'fs';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import os from 'os';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
24
|
+
import { createRequire } from 'module';
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = path.dirname(__filename);
|
|
28
|
+
const PACKAGE_DIR = path.join(__dirname, '..');
|
|
29
|
+
|
|
30
|
+
// ─── Structured Logger ────────────────────────────────────────────────────────
|
|
31
|
+
// Replaces verbose npm/node output with clean phase-aware status lines.
|
|
32
|
+
|
|
33
|
+
const PHASES = {
|
|
34
|
+
ENV: '[ 1/7 ] Detecting environment',
|
|
35
|
+
DIR: '[ 2/7 ] Creating project directory',
|
|
36
|
+
SCAF: '[ 3/7 ] Scaffolding project files',
|
|
37
|
+
DEPS: '[ 4/7 ] Installing dependencies',
|
|
38
|
+
SETUP: '[ 5/7 ] Configuring GigaClaw',
|
|
39
|
+
ENV_W: '[ 6/7 ] Writing .env',
|
|
40
|
+
START: '[ 7/7 ] Starting dev server',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function log(phase, msg) {
|
|
44
|
+
const ts = new Date().toLocaleTimeString('en-US', { hour12: false });
|
|
45
|
+
process.stdout.write(`\n ${phase}\n ${ts} ${msg}\n`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function logStep(msg) {
|
|
49
|
+
process.stdout.write(` → ${msg}\n`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function logOk(msg) {
|
|
53
|
+
process.stdout.write(` ✓ ${msg}\n`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logWarn(msg) {
|
|
57
|
+
process.stdout.write(` ⚠ ${msg}\n`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function logErr(msg) {
|
|
61
|
+
process.stderr.write(` ✗ ${msg}\n`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printBanner() {
|
|
65
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_DIR, 'package.json'), 'utf8'));
|
|
66
|
+
console.log(`
|
|
67
|
+
_______ ________
|
|
68
|
+
/ ____(_)___ _____ _/ ____/ /___ _ __
|
|
69
|
+
/ / __/ / __ \\/ __ \`/ / / / __ \\ | /| / /
|
|
70
|
+
/ /_/ / / /_/ / /_/ / /___/ / /_/ / |/ |/ /
|
|
71
|
+
\\____/_/\\__, /\\__,_/\\____/_/\\____/|__/|__/
|
|
72
|
+
/____/
|
|
73
|
+
|
|
74
|
+
India's Autonomous AI Agent · Powered by Gignaati
|
|
75
|
+
v${pkg.version} — One-Command Bootstrap
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─── Section 1: Reliable Dependency Installation ─────────────────────────────
|
|
80
|
+
|
|
81
|
+
const SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
82
|
+
|
|
83
|
+
async function installDependencies(cwd) {
|
|
84
|
+
const MAX_ATTEMPTS = 3;
|
|
85
|
+
const BACKOFF = [2000, 5000, 10000]; // exponential: 2s, 5s, 10s
|
|
86
|
+
|
|
87
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
88
|
+
try {
|
|
89
|
+
logStep(`npm install attempt ${attempt}/${MAX_ATTEMPTS}...`);
|
|
90
|
+
|
|
91
|
+
// On retry: clean cache and remove node_modules + lock file to start fresh
|
|
92
|
+
if (attempt > 1) {
|
|
93
|
+
logStep('Cleaning npm cache before retry...');
|
|
94
|
+
try {
|
|
95
|
+
execSync('npm cache clean --force', { stdio: 'pipe', cwd, shell: true });
|
|
96
|
+
} catch (_) { /* non-fatal */ }
|
|
97
|
+
|
|
98
|
+
const nmPath = path.join(cwd, 'node_modules');
|
|
99
|
+
const lockPath = path.join(cwd, 'package-lock.json');
|
|
100
|
+
if (fs.existsSync(nmPath)) {
|
|
101
|
+
logStep('Removing node_modules...');
|
|
102
|
+
fs.rmSync(nmPath, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
if (fs.existsSync(lockPath)) {
|
|
105
|
+
logStep('Removing package-lock.json...');
|
|
106
|
+
fs.rmSync(lockPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const delay = BACKOFF[attempt - 2] || 10000;
|
|
110
|
+
logStep(`Waiting ${delay / 1000}s before retry...`);
|
|
111
|
+
await SLEEP(delay);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
execSync(
|
|
115
|
+
'npm install --no-audit --no-fund --prefer-online',
|
|
116
|
+
{ stdio: 'pipe', cwd, shell: true }
|
|
117
|
+
);
|
|
118
|
+
logOk('Dependencies installed successfully.');
|
|
119
|
+
return;
|
|
120
|
+
} catch (err) {
|
|
121
|
+
logWarn(`npm install attempt ${attempt} failed: ${err.message.split('\n')[0]}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// All npm attempts failed — try pnpm fallback
|
|
126
|
+
logWarn('All npm attempts failed. Trying pnpm fallback...');
|
|
127
|
+
try {
|
|
128
|
+
execSync('pnpm --version', { stdio: 'pipe', shell: true });
|
|
129
|
+
logStep('pnpm detected — running pnpm install...');
|
|
130
|
+
execSync('pnpm install --prefer-offline', { stdio: 'inherit', cwd, shell: true });
|
|
131
|
+
logOk('Dependencies installed via pnpm.');
|
|
132
|
+
return;
|
|
133
|
+
} catch (_) {
|
|
134
|
+
logErr('pnpm not available or also failed.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
logErr('Dependency installation failed after 3 attempts + pnpm fallback.');
|
|
138
|
+
logErr('Please check your network connection and run: npm install');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ─── Section 2: Environment Detection ────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
async function detectEnvironment() {
|
|
145
|
+
const env = {
|
|
146
|
+
nodeVersion: process.version,
|
|
147
|
+
platform: process.platform,
|
|
148
|
+
docker: false,
|
|
149
|
+
ollama: false,
|
|
150
|
+
ollamaModels: [],
|
|
151
|
+
ramGb: Math.floor(os.totalmem() / (1024 ** 3)),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Docker
|
|
155
|
+
try {
|
|
156
|
+
execSync('docker --version', { stdio: 'pipe', shell: true });
|
|
157
|
+
env.docker = true;
|
|
158
|
+
} catch (_) {}
|
|
159
|
+
|
|
160
|
+
// Ollama
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch('http://localhost:11434/api/tags', {
|
|
163
|
+
signal: AbortSignal.timeout(3000),
|
|
164
|
+
});
|
|
165
|
+
if (res.ok) {
|
|
166
|
+
env.ollama = true;
|
|
167
|
+
const data = await res.json();
|
|
168
|
+
env.ollamaModels = (data.models || []).map((m) => m.name);
|
|
169
|
+
}
|
|
170
|
+
} catch (_) {}
|
|
171
|
+
|
|
172
|
+
return env;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function recommendOllamaModel(ramGb) {
|
|
176
|
+
if (ramGb >= 32) return 'llama3.1:8b';
|
|
177
|
+
if (ramGb >= 16) return 'llama3.1:8b';
|
|
178
|
+
return 'llama3.2:3b';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── Section 2: Smart Defaults Setup (non-interactive) ───────────────────────
|
|
182
|
+
|
|
183
|
+
async function runSmartSetup(cwd, envInfo) {
|
|
184
|
+
const { randomBytes } = await import('crypto');
|
|
185
|
+
|
|
186
|
+
const authSecret = randomBytes(32).toString('base64url');
|
|
187
|
+
const nextAuthSecret = randomBytes(32).toString('base64url');
|
|
188
|
+
|
|
189
|
+
const localModel = envInfo.ollamaModels.length > 0
|
|
190
|
+
? envInfo.ollamaModels[0]
|
|
191
|
+
: recommendOllamaModel(envInfo.ramGb);
|
|
192
|
+
|
|
193
|
+
const envVars = {
|
|
194
|
+
// Mode
|
|
195
|
+
GIGACLAW_MODE: 'hybrid',
|
|
196
|
+
|
|
197
|
+
// Cloud: default to Claude Sonnet (user can change via npm run setup)
|
|
198
|
+
LLM_PROVIDER: 'anthropic',
|
|
199
|
+
LLM_MODEL: 'claude-sonnet-4-6',
|
|
200
|
+
// Note: ANTHROPIC_API_KEY intentionally left blank — user must provide it
|
|
201
|
+
ANTHROPIC_API_KEY: '',
|
|
202
|
+
|
|
203
|
+
// Local: Ollama if running, else blank
|
|
204
|
+
LOCAL_LLM_PROVIDER: envInfo.ollama ? 'ollama' : '',
|
|
205
|
+
LOCAL_LLM_MODEL: envInfo.ollama ? localModel : '',
|
|
206
|
+
OLLAMA_BASE_URL: 'http://localhost:11434',
|
|
207
|
+
|
|
208
|
+
// Routing
|
|
209
|
+
HYBRID_ROUTING: 'auto',
|
|
210
|
+
|
|
211
|
+
// Auth
|
|
212
|
+
NEXTAUTH_URL: 'http://localhost:3000',
|
|
213
|
+
NEXTAUTH_SECRET: nextAuthSecret,
|
|
214
|
+
AUTH_SECRET: authSecret,
|
|
215
|
+
AUTH_TRUST_HOST: 'true',
|
|
216
|
+
|
|
217
|
+
// Version
|
|
218
|
+
GIGACLAW_VERSION: JSON.parse(
|
|
219
|
+
fs.readFileSync(path.join(PACKAGE_DIR, 'package.json'), 'utf8')
|
|
220
|
+
).version,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Write .env
|
|
224
|
+
const envPath = path.join(cwd, '.env');
|
|
225
|
+
let content = '';
|
|
226
|
+
if (fs.existsSync(envPath)) {
|
|
227
|
+
content = fs.readFileSync(envPath, 'utf-8');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
231
|
+
// Don't overwrite existing non-empty values (preserves user's API keys on re-run)
|
|
232
|
+
const regex = new RegExp(`^${key}=(.*)$`, 'm');
|
|
233
|
+
const match = content.match(regex);
|
|
234
|
+
if (match && match[1].trim()) {
|
|
235
|
+
// Already set — skip
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (regex.test(content)) {
|
|
239
|
+
content = content.replace(regex, `${key}=${value}`);
|
|
240
|
+
} else {
|
|
241
|
+
content = content.trimEnd() + `\n${key}=${value}\n`;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
fs.writeFileSync(envPath, content);
|
|
246
|
+
return { localModel, envVars };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ─── Section 4+5: Auto Start Dev Server + Auto Open Browser ─────────────────
|
|
250
|
+
|
|
251
|
+
function openBrowser(url) {
|
|
252
|
+
const platform = process.platform;
|
|
253
|
+
try {
|
|
254
|
+
if (platform === 'win32') {
|
|
255
|
+
execSync(`start ${url}`, { stdio: 'pipe', shell: true });
|
|
256
|
+
} else if (platform === 'darwin') {
|
|
257
|
+
execSync(`open ${url}`, { stdio: 'pipe', shell: true });
|
|
258
|
+
} else {
|
|
259
|
+
execSync(`xdg-open ${url}`, { stdio: 'pipe', shell: true });
|
|
260
|
+
}
|
|
261
|
+
logOk(`Browser opened at ${url}`);
|
|
262
|
+
} catch (_) {
|
|
263
|
+
logWarn(`Could not auto-open browser. Visit manually: ${url}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function startDevServer(cwd) {
|
|
268
|
+
const APP_URL = 'http://localhost:3000';
|
|
269
|
+
|
|
270
|
+
logStep('Launching Next.js dev server (turbopack)...');
|
|
271
|
+
|
|
272
|
+
const child = spawn('npm', ['run', 'dev'], {
|
|
273
|
+
cwd,
|
|
274
|
+
shell: true,
|
|
275
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
276
|
+
detached: false,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
let serverReady = false;
|
|
280
|
+
let output = '';
|
|
281
|
+
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
const timeout = setTimeout(() => {
|
|
284
|
+
if (!serverReady) {
|
|
285
|
+
logWarn('Server startup timed out after 120s. Check logs manually.');
|
|
286
|
+
logWarn(`Run: cd ${cwd} && npm run dev`);
|
|
287
|
+
resolve();
|
|
288
|
+
}
|
|
289
|
+
}, 120_000);
|
|
290
|
+
|
|
291
|
+
function onData(chunk) {
|
|
292
|
+
const text = chunk.toString();
|
|
293
|
+
output += text;
|
|
294
|
+
|
|
295
|
+
// Forward server output with indent
|
|
296
|
+
for (const line of text.split('\n')) {
|
|
297
|
+
if (line.trim()) process.stdout.write(` ${line}\n`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Detect "Local: http://localhost:3000" — server is ready
|
|
301
|
+
if (!serverReady && (
|
|
302
|
+
text.includes('Local:') && text.includes('localhost:3000') ||
|
|
303
|
+
text.includes('Ready in') ||
|
|
304
|
+
text.includes('✓ Ready')
|
|
305
|
+
)) {
|
|
306
|
+
serverReady = true;
|
|
307
|
+
clearTimeout(timeout);
|
|
308
|
+
logOk(`Dev server ready at ${APP_URL}`);
|
|
309
|
+
openBrowser(APP_URL);
|
|
310
|
+
resolve(child);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
child.stdout.on('data', onData);
|
|
315
|
+
child.stderr.on('data', onData);
|
|
316
|
+
|
|
317
|
+
child.on('error', (err) => {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
logErr(`Dev server failed to start: ${err.message}`);
|
|
320
|
+
reject(err);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
child.on('exit', (code) => {
|
|
324
|
+
clearTimeout(timeout);
|
|
325
|
+
if (!serverReady) {
|
|
326
|
+
logErr(`Dev server exited with code ${code}`);
|
|
327
|
+
reject(new Error(`Server exited with code ${code}`));
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ─── Main Bootstrap Orchestrator ─────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
export async function bootstrap() {
|
|
336
|
+
const interactive = process.argv.includes('--interactive');
|
|
337
|
+
|
|
338
|
+
printBanner();
|
|
339
|
+
|
|
340
|
+
// ── Phase 1: Detect environment ──────────────────────────────────────────
|
|
341
|
+
log(PHASES.ENV, 'Checking Node.js, Docker, Ollama...');
|
|
342
|
+
|
|
343
|
+
const envInfo = await detectEnvironment();
|
|
344
|
+
|
|
345
|
+
logOk(`Node.js ${envInfo.nodeVersion}`);
|
|
346
|
+
logOk(`Platform: ${envInfo.platform} | RAM: ${envInfo.ramGb} GB`);
|
|
347
|
+
|
|
348
|
+
if (envInfo.docker) {
|
|
349
|
+
logOk('Docker: available');
|
|
350
|
+
} else {
|
|
351
|
+
logWarn('Docker: not found (optional — needed for cloud agent mode)');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (envInfo.ollama) {
|
|
355
|
+
logOk(`Ollama: running — ${envInfo.ollamaModels.length} model(s) available`);
|
|
356
|
+
if (envInfo.ollamaModels.length > 0) {
|
|
357
|
+
logStep(`Models: ${envInfo.ollamaModels.slice(0, 3).join(', ')}${envInfo.ollamaModels.length > 3 ? '...' : ''}`);
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
logWarn('Ollama: not running (optional — enables local AI mode)');
|
|
361
|
+
logStep('Install: https://ollama.com/download');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── Phase 2: Determine project directory ─────────────────────────────────
|
|
365
|
+
log(PHASES.DIR, 'Preparing project directory...');
|
|
366
|
+
|
|
367
|
+
let cwd = process.cwd();
|
|
368
|
+
const entries = fs.readdirSync(cwd).filter(e => !e.startsWith('.'));
|
|
369
|
+
|
|
370
|
+
if (entries.length > 0) {
|
|
371
|
+
// Non-empty directory — check if it's already a gigaclaw project
|
|
372
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
373
|
+
let isExistingProject = false;
|
|
374
|
+
if (fs.existsSync(pkgPath)) {
|
|
375
|
+
try {
|
|
376
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
377
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
378
|
+
if (deps.gigaclaw) isExistingProject = true;
|
|
379
|
+
} catch (_) {}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!isExistingProject) {
|
|
383
|
+
const dirName = 'gigaclaw-app';
|
|
384
|
+
const newDir = path.resolve(cwd, dirName);
|
|
385
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
386
|
+
process.chdir(newDir);
|
|
387
|
+
cwd = newDir;
|
|
388
|
+
logOk(`Created ${dirName}/ (current directory was not empty)`);
|
|
389
|
+
} else {
|
|
390
|
+
logOk(`Existing GigaClaw project detected — updating in place`);
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
logOk(`Using current directory: ${path.basename(cwd)}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ── Phase 3: Scaffolding ─────────────────────────────────────────────────
|
|
397
|
+
log(PHASES.SCAF, 'Copying project templates...');
|
|
398
|
+
|
|
399
|
+
// Dynamically import init logic from cli.js by re-using the scaffolding function
|
|
400
|
+
// We call the init logic directly to avoid spawning a child process
|
|
401
|
+
const { scaffoldProject } = await import('./scaffold.mjs');
|
|
402
|
+
const { created, updated } = await scaffoldProject(cwd, PACKAGE_DIR);
|
|
403
|
+
|
|
404
|
+
if (created.length > 0) logOk(`Created ${created.length} file(s)`);
|
|
405
|
+
if (updated.length > 0) logOk(`Updated ${updated.length} managed file(s)`);
|
|
406
|
+
if (created.length === 0 && updated.length === 0) logOk('All files up to date');
|
|
407
|
+
|
|
408
|
+
// ── Phase 4: Install dependencies ───────────────────────────────────────
|
|
409
|
+
log(PHASES.DEPS, 'Installing npm packages (retry-safe)...');
|
|
410
|
+
await installDependencies(cwd);
|
|
411
|
+
|
|
412
|
+
// ── Phase 5+6: Setup + .env ──────────────────────────────────────────────
|
|
413
|
+
if (interactive) {
|
|
414
|
+
log(PHASES.SETUP, 'Launching interactive setup wizard...');
|
|
415
|
+
const setupScript = path.join(PACKAGE_DIR, 'setup', 'setup.mjs');
|
|
416
|
+
try {
|
|
417
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd });
|
|
418
|
+
} catch (e) {
|
|
419
|
+
logErr(`Setup wizard failed: ${e.message}`);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
log(PHASES.SETUP, 'Applying smart defaults (hybrid mode, Claude Sonnet, auto routing)...');
|
|
424
|
+
const { localModel } = await runSmartSetup(cwd, envInfo);
|
|
425
|
+
|
|
426
|
+
log(PHASES.ENV_W, 'Writing .env configuration...');
|
|
427
|
+
logOk('GIGACLAW_MODE=hybrid');
|
|
428
|
+
logOk('LLM_PROVIDER=anthropic | LLM_MODEL=claude-sonnet-4-6');
|
|
429
|
+
logOk(`LOCAL_LLM_PROVIDER=${envInfo.ollama ? 'ollama' : '(not configured)'} | LOCAL_LLM_MODEL=${envInfo.ollama ? localModel : '(not configured)'}`);
|
|
430
|
+
logOk('HYBRID_ROUTING=auto');
|
|
431
|
+
logWarn('ANTHROPIC_API_KEY is empty — run: npm run setup to add your API key');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── Phase 7: Start dev server + open browser ─────────────────────────────
|
|
435
|
+
log(PHASES.START, 'Starting Next.js dev server...');
|
|
436
|
+
|
|
437
|
+
console.log(`
|
|
438
|
+
┌─────────────────────────────────────────────────────────┐
|
|
439
|
+
│ │
|
|
440
|
+
│ GigaClaw is starting... │
|
|
441
|
+
│ │
|
|
442
|
+
│ App URL: http://localhost:3000 │
|
|
443
|
+
│ Mode: Hybrid (Cloud + Local) │
|
|
444
|
+
│ │
|
|
445
|
+
│ Next step: add your ANTHROPIC_API_KEY to .env │
|
|
446
|
+
│ or run: npm run setup for the full wizard │
|
|
447
|
+
│ │
|
|
448
|
+
└─────────────────────────────────────────────────────────┘
|
|
449
|
+
`);
|
|
450
|
+
|
|
451
|
+
await startDevServer(cwd);
|
|
452
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { execSync, execFileSync } from 'child_process';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
|
+
import os from 'os';
|
|
6
7
|
import { fileURLToPath } from 'url';
|
|
7
8
|
import { createDirLink } from '../setup/lib/fs-utils.mjs';
|
|
8
9
|
|
|
@@ -81,9 +82,13 @@ function parseUpgradeTarget(arg) {
|
|
|
81
82
|
|
|
82
83
|
function printUsage() {
|
|
83
84
|
console.log(`
|
|
84
|
-
Usage: gigaclaw
|
|
85
|
+
Usage: gigaclaw [command]
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
One-command bootstrap (recommended):
|
|
88
|
+
npx gigaclaw@latest Full auto-setup: scaffold + install + configure + start
|
|
89
|
+
npx gigaclaw@latest --interactive Same, but with interactive setup wizard
|
|
90
|
+
|
|
91
|
+
Manual commands:
|
|
87
92
|
init Scaffold a new gigaclaw project
|
|
88
93
|
upgrade|update [@beta|version] Upgrade gigaclaw (install, init, build, commit, push)
|
|
89
94
|
setup Run interactive setup wizard
|
|
@@ -94,7 +99,7 @@ Commands:
|
|
|
94
99
|
set-agent-secret <KEY> [VALUE] Set a GitHub secret with AGENT_ prefix (also updates .env)
|
|
95
100
|
set-agent-llm-secret <KEY> [VALUE] Set a GitHub secret with AGENT_LLM_ prefix
|
|
96
101
|
set-var <KEY> [VALUE] Set a GitHub repository variable
|
|
97
|
-
|
|
102
|
+
--version, -v Show gigaclaw version
|
|
98
103
|
|
|
99
104
|
Powered by Gignaati — https://www.gignaati.com
|
|
100
105
|
`);
|
|
@@ -307,10 +312,9 @@ async function init() {
|
|
|
307
312
|
console.log(' To reset to default: npx gigaclaw reset <file>');
|
|
308
313
|
}
|
|
309
314
|
|
|
310
|
-
// Run npm install
|
|
315
|
+
// Run npm install with retry-safe logic
|
|
311
316
|
console.log('\nInstalling dependencies...\n');
|
|
312
|
-
|
|
313
|
-
execSync('npm install', { stdio: 'inherit', cwd, shell: true });
|
|
317
|
+
await installDependenciesWithRetry(cwd);
|
|
314
318
|
|
|
315
319
|
// Create or update .env with auto-generated infrastructure values
|
|
316
320
|
const envPath = path.join(cwd, '.env');
|
|
@@ -348,6 +352,51 @@ GIGACLAW_VERSION=${version}
|
|
|
348
352
|
console.log('\nDone! Run: npm run setup\n');
|
|
349
353
|
}
|
|
350
354
|
|
|
355
|
+
/**
|
|
356
|
+
* Retry-safe npm install with cache clean, exponential backoff, and pnpm fallback.
|
|
357
|
+
* Used by both `gigaclaw init` and the one-command bootstrap.
|
|
358
|
+
*/
|
|
359
|
+
async function installDependenciesWithRetry(cwd) {
|
|
360
|
+
const MAX_ATTEMPTS = 3;
|
|
361
|
+
const BACKOFF = [2000, 5000, 10000];
|
|
362
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
363
|
+
|
|
364
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
365
|
+
try {
|
|
366
|
+
if (attempt > 1) {
|
|
367
|
+
console.log(` Cleaning npm cache before retry (attempt ${attempt})...`);
|
|
368
|
+
try { execSync('npm cache clean --force', { stdio: 'pipe', cwd, shell: true }); } catch (_) {}
|
|
369
|
+
const nmPath = path.join(cwd, 'node_modules');
|
|
370
|
+
const lockPath = path.join(cwd, 'package-lock.json');
|
|
371
|
+
if (fs.existsSync(nmPath)) {
|
|
372
|
+
console.log(' Removing node_modules...');
|
|
373
|
+
fs.rmSync(nmPath, { recursive: true, force: true });
|
|
374
|
+
}
|
|
375
|
+
if (fs.existsSync(lockPath)) fs.rmSync(lockPath);
|
|
376
|
+
const delay = BACKOFF[attempt - 2] || 10000;
|
|
377
|
+
console.log(` Waiting ${delay / 1000}s...`);
|
|
378
|
+
await sleep(delay);
|
|
379
|
+
}
|
|
380
|
+
execSync('npm install --no-audit --no-fund --prefer-online', { stdio: 'inherit', cwd, shell: true });
|
|
381
|
+
return; // success
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.warn(` ⚠ npm install attempt ${attempt} failed: ${err.message.split('\n')[0]}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// pnpm fallback
|
|
388
|
+
console.warn(' All npm attempts failed. Trying pnpm fallback...');
|
|
389
|
+
try {
|
|
390
|
+
execSync('pnpm --version', { stdio: 'pipe', shell: true });
|
|
391
|
+
execSync('pnpm install --prefer-offline', { stdio: 'inherit', cwd, shell: true });
|
|
392
|
+
console.log(' ✓ Dependencies installed via pnpm.');
|
|
393
|
+
return;
|
|
394
|
+
} catch (_) {}
|
|
395
|
+
|
|
396
|
+
console.error(' ✗ Dependency installation failed. Check your network and run: npm install');
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
|
|
351
400
|
/**
|
|
352
401
|
* List all available template files, or restore a specific one.
|
|
353
402
|
*/
|
|
@@ -798,6 +847,15 @@ async function setVar(key, value) {
|
|
|
798
847
|
}
|
|
799
848
|
|
|
800
849
|
switch (command) {
|
|
850
|
+
case undefined:
|
|
851
|
+
case null:
|
|
852
|
+
case '':
|
|
853
|
+
case '--interactive': {
|
|
854
|
+
// No subcommand — run one-command bootstrap
|
|
855
|
+
const { bootstrap } = await import('./bootstrap.mjs');
|
|
856
|
+
await bootstrap();
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
801
859
|
case 'init':
|
|
802
860
|
await init();
|
|
803
861
|
break;
|
|
@@ -831,5 +889,5 @@ switch (command) {
|
|
|
831
889
|
break;
|
|
832
890
|
default:
|
|
833
891
|
printUsage();
|
|
834
|
-
process.exit(
|
|
892
|
+
process.exit(1);
|
|
835
893
|
}
|