gigaclaw 1.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 +26 -0
- package/README.md +237 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +265 -0
- package/bin/cli.js +823 -0
- package/bin/local.sh +85 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +26 -0
- package/config/instrumentation.js +62 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_nostalgic_sersi.sql +11 -0
- package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
- package/drizzle/0003_rename_code_workspaces.sql +5 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0001_snapshot.json +390 -0
- package/drizzle/meta/0002_snapshot.json +411 -0
- package/drizzle/meta/0003_snapshot.json +419 -0
- package/drizzle/meta/_journal.json +34 -0
- package/lib/actions.js +44 -0
- package/lib/ai/agent.js +86 -0
- package/lib/ai/index.js +342 -0
- package/lib/ai/model.js +180 -0
- package/lib/ai/tools.js +269 -0
- package/lib/ai/web-search.js +42 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +62 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +579 -0
- package/lib/chat/api.js +140 -0
- package/lib/chat/components/app-sidebar.js +213 -0
- package/lib/chat/components/app-sidebar.jsx +279 -0
- package/lib/chat/components/chat-header.js +192 -0
- package/lib/chat/components/chat-header.jsx +223 -0
- package/lib/chat/components/chat-input.js +236 -0
- package/lib/chat/components/chat-input.jsx +249 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +99 -0
- package/lib/chat/components/chat-page.jsx +121 -0
- package/lib/chat/components/chat.js +153 -0
- package/lib/chat/components/chat.jsx +199 -0
- package/lib/chat/components/chats-page.js +367 -0
- package/lib/chat/components/chats-page.jsx +394 -0
- package/lib/chat/components/code-mode-toggle.js +132 -0
- package/lib/chat/components/code-mode-toggle.jsx +163 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/greeting.js +11 -0
- package/lib/chat/components/greeting.jsx +16 -0
- package/lib/chat/components/icons.js +805 -0
- package/lib/chat/components/icons.jsx +751 -0
- package/lib/chat/components/index.js +20 -0
- package/lib/chat/components/message.js +363 -0
- package/lib/chat/components/message.jsx +422 -0
- package/lib/chat/components/messages.js +65 -0
- package/lib/chat/components/messages.jsx +74 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/pull-requests-page.js +103 -0
- package/lib/chat/components/pull-requests-page.jsx +113 -0
- package/lib/chat/components/settings-layout.js +39 -0
- package/lib/chat/components/settings-layout.jsx +53 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +138 -0
- package/lib/chat/components/sidebar-history-item.jsx +119 -0
- package/lib/chat/components/sidebar-history.js +167 -0
- package/lib/chat/components/sidebar-history.jsx +220 -0
- package/lib/chat/components/sidebar-user-nav.js +61 -0
- package/lib/chat/components/sidebar-user-nav.jsx +77 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/tool-call.js +89 -0
- package/lib/chat/components/tool-call.jsx +107 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/combobox.js +98 -0
- package/lib/chat/components/ui/combobox.jsx +114 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +194 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
- package/lib/chat/components/ui/rename-dialog.js +78 -0
- package/lib/chat/components/ui/rename-dialog.jsx +74 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +228 -0
- package/lib/chat/components/ui/sidebar.jsx +246 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/code/actions.js +153 -0
- package/lib/code/code-page.js +22 -0
- package/lib/code/code-page.jsx +25 -0
- package/lib/code/index.js +1 -0
- package/lib/code/terminal-view.js +201 -0
- package/lib/code/terminal-view.jsx +224 -0
- package/lib/code/ws-proxy.js +80 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +168 -0
- package/lib/db/code-workspaces.js +110 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +66 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/paths.js +42 -0
- package/lib/tools/create-job.js +97 -0
- package/lib/tools/docker.js +146 -0
- package/lib/tools/github.js +271 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +104 -0
- package/lib/utils/render-md.js +111 -0
- package/package.json +118 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +105 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-cloud.mjs +833 -0
- package/setup/setup-local.mjs +377 -0
- package/setup/setup-telegram.mjs +265 -0
- package/setup/setup.mjs +87 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +104 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +308 -0
- package/templates/app/api/[...gigaclaw]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +9 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
- package/templates/app/components/ascii-logo.jsx +12 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +33 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/pull-requests/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/CODE_PLANNING.md +14 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_AGENT.md +30 -0
- package/templates/config/JOB_PLANNING.md +240 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
- package/templates/config/SOUL.md +48 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
- package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
- package/templates/docker/claude-code-job/Dockerfile +34 -0
- package/templates/docker/claude-code-job/entrypoint.sh +149 -0
- package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
- package/templates/docker/claude-code-workspace/Dockerfile +61 -0
- package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
- package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
- package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
- package/templates/docker-compose.local.yml +78 -0
- package/templates/docker-compose.yml +64 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +23 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/public/favicon.ico +0 -0
- package/templates/server.js +25 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/google-docs/SKILL.md +23 -0
- package/templates/skills/google-docs/create.sh +69 -0
- package/templates/skills/google-drive/SKILL.md +47 -0
- package/templates/skills/google-drive/delete.sh +47 -0
- package/templates/skills/google-drive/download.sh +50 -0
- package/templates/skills/google-drive/list.sh +41 -0
- package/templates/skills/google-drive/upload.sh +76 -0
- package/templates/skills/kie-ai/SKILL.md +38 -0
- package/templates/skills/kie-ai/generate-image.sh +77 -0
- package/templates/skills/kie-ai/generate-video.sh +69 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
- package/templates/skills/youtube-transcript/SKILL.md +41 -0
- package/templates/skills/youtube-transcript/package-lock.json +24 -0
- package/templates/skills/youtube-transcript/package.json +8 -0
- package/templates/skills/youtube-transcript/transcript.js +84 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync, execFileSync } from 'child_process';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { createDirLink } from '../setup/lib/fs-utils.mjs';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const command = process.argv[2];
|
|
13
|
+
const args = process.argv.slice(3);
|
|
14
|
+
|
|
15
|
+
// Handle --version / -v flag
|
|
16
|
+
if (command === '--version' || command === '-v') {
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
18
|
+
console.log(`gigaclaw v${pkg.version}`);
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Files tightly coupled to the package version that are auto-updated by init.
|
|
23
|
+
// These live in the user's project because GitHub/Docker require them at specific paths,
|
|
24
|
+
// but they shouldn't drift from the package version.
|
|
25
|
+
const MANAGED_PATHS = [
|
|
26
|
+
'.github/workflows/',
|
|
27
|
+
'docker/event-handler/',
|
|
28
|
+
'docker-compose.yml',
|
|
29
|
+
'docker-compose.local.yml',
|
|
30
|
+
'.dockerignore',
|
|
31
|
+
'CLAUDE.md',
|
|
32
|
+
// middleware.js must always be kept in sync with the package template because
|
|
33
|
+
// Next.js / Turbopack requires the `config` export to be a static literal
|
|
34
|
+
// object defined directly in this file — it cannot be re-exported from a
|
|
35
|
+
// module. Keeping it managed ensures users always get the correct pattern.
|
|
36
|
+
'middleware.js',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Files that are only relevant in cloud mode (GitHub + ngrok + Telegram).
|
|
40
|
+
// In local mode these are skipped during scaffolding to keep the project clean.
|
|
41
|
+
const CLOUD_ONLY_PATHS = [
|
|
42
|
+
'.github/workflows/',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
function isManaged(relPath) {
|
|
46
|
+
return MANAGED_PATHS.some(p => relPath === p || relPath.startsWith(p));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Files that must never be scaffolded directly (use .template suffix instead).
|
|
50
|
+
const EXCLUDED_FILENAMES = ['CLAUDE.md'];
|
|
51
|
+
|
|
52
|
+
// Files ending in .template are scaffolded with the suffix stripped.
|
|
53
|
+
// e.g. .gitignore.template → .gitignore, CLAUDE.md.template → CLAUDE.md
|
|
54
|
+
function destPath(templateRelPath) {
|
|
55
|
+
if (templateRelPath.endsWith('.template')) {
|
|
56
|
+
return templateRelPath.slice(0, -'.template'.length);
|
|
57
|
+
}
|
|
58
|
+
return templateRelPath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function templatePath(userPath, templatesDir) {
|
|
62
|
+
const withSuffix = userPath + '.template';
|
|
63
|
+
if (fs.existsSync(path.join(templatesDir, withSuffix))) {
|
|
64
|
+
return withSuffix;
|
|
65
|
+
}
|
|
66
|
+
return userPath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse upgrade target from CLI arg into an npm install specifier.
|
|
71
|
+
* Examples: undefined → "latest", "@beta" → "beta", "@rc" → "rc", "1.2.72" → "1.2.72"
|
|
72
|
+
*/
|
|
73
|
+
function parseUpgradeTarget(arg) {
|
|
74
|
+
if (!arg) return 'latest';
|
|
75
|
+
if (arg.startsWith('@')) return arg.slice(1); // @beta → beta, @rc → rc, @latest → latest
|
|
76
|
+
return arg; // bare version like 1.2.72
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function printUsage() {
|
|
80
|
+
console.log(`
|
|
81
|
+
Usage: gigaclaw <command>
|
|
82
|
+
|
|
83
|
+
Commands:
|
|
84
|
+
init Scaffold a new gigaclaw project
|
|
85
|
+
upgrade|update [@beta|version] Upgrade gigaclaw (install, init, build, commit, push)
|
|
86
|
+
setup Run interactive setup wizard
|
|
87
|
+
setup-telegram Reconfigure Telegram webhook
|
|
88
|
+
reset-auth Regenerate AUTH_SECRET (invalidates all sessions)
|
|
89
|
+
reset [file] Restore a template file (or list available templates)
|
|
90
|
+
diff [file] Show differences between project files and package templates
|
|
91
|
+
set-agent-secret <KEY> [VALUE] Set a GitHub secret with AGENT_ prefix (also updates .env)
|
|
92
|
+
set-agent-llm-secret <KEY> [VALUE] Set a GitHub secret with AGENT_LLM_ prefix
|
|
93
|
+
set-var <KEY> [VALUE] Set a GitHub repository variable
|
|
94
|
+
--version, -v Show gigaclaw version
|
|
95
|
+
|
|
96
|
+
Powered by Gignaati — https://www.gignaati.com
|
|
97
|
+
`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Collect all template files as relative paths.
|
|
102
|
+
*/
|
|
103
|
+
function getTemplateFiles(templatesDir) {
|
|
104
|
+
const files = [];
|
|
105
|
+
function walk(dir) {
|
|
106
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
const fullPath = path.join(dir, entry.name);
|
|
109
|
+
if (entry.isDirectory()) {
|
|
110
|
+
walk(fullPath);
|
|
111
|
+
} else if (!EXCLUDED_FILENAMES.includes(entry.name)) {
|
|
112
|
+
files.push(path.relative(templatesDir, fullPath));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
walk(templatesDir);
|
|
117
|
+
return files;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function init() {
|
|
121
|
+
let cwd = process.cwd();
|
|
122
|
+
const packageDir = path.join(__dirname, '..');
|
|
123
|
+
const templatesDir = path.join(packageDir, 'templates');
|
|
124
|
+
const noManaged = args.includes('--no-managed');
|
|
125
|
+
|
|
126
|
+
// Guard: warn if the directory is not empty (unless it's an existing gigaclaw project)
|
|
127
|
+
const entries = fs.readdirSync(cwd);
|
|
128
|
+
if (entries.length > 0) {
|
|
129
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
130
|
+
let isExistingProject = false;
|
|
131
|
+
if (fs.existsSync(pkgPath)) {
|
|
132
|
+
try {
|
|
133
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
134
|
+
const deps = pkg.dependencies || {};
|
|
135
|
+
const devDeps = pkg.devDependencies || {};
|
|
136
|
+
if (deps.gigaclaw || devDeps.gigaclaw) {
|
|
137
|
+
isExistingProject = true;
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!isExistingProject) {
|
|
143
|
+
console.log('\nThis directory is not empty.');
|
|
144
|
+
const { text, isCancel } = await import('@clack/prompts');
|
|
145
|
+
const dirName = await text({
|
|
146
|
+
message: 'Project directory name:',
|
|
147
|
+
defaultValue: 'my-gigaclaw',
|
|
148
|
+
});
|
|
149
|
+
if (isCancel(dirName)) {
|
|
150
|
+
console.log('\nCancelled.\n');
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
const newDir = path.resolve(cwd, dirName);
|
|
154
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
155
|
+
process.chdir(newDir);
|
|
156
|
+
cwd = newDir;
|
|
157
|
+
console.log(`\nCreated ${dirName}/`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('\nScaffolding gigaclaw project...\n');
|
|
162
|
+
|
|
163
|
+
const templateFiles = getTemplateFiles(templatesDir);
|
|
164
|
+
const created = [];
|
|
165
|
+
const skipped = [];
|
|
166
|
+
const changed = [];
|
|
167
|
+
const updated = [];
|
|
168
|
+
|
|
169
|
+
// Detect mode from existing .env (if any) so re-running init respects the chosen mode
|
|
170
|
+
const existingEnvPath = path.join(cwd, '.env');
|
|
171
|
+
let gigaclawMode = 'cloud';
|
|
172
|
+
if (fs.existsSync(existingEnvPath)) {
|
|
173
|
+
const envContent = fs.readFileSync(existingEnvPath, 'utf-8');
|
|
174
|
+
const modeMatch = envContent.match(/^GIGACLAW_MODE=(.*)$/m);
|
|
175
|
+
if (modeMatch && modeMatch[1].trim() === 'local') gigaclawMode = 'local';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const relPath of templateFiles) {
|
|
179
|
+
const src = path.join(templatesDir, relPath);
|
|
180
|
+
const outPath = destPath(relPath);
|
|
181
|
+
const dest = path.join(cwd, outPath);
|
|
182
|
+
|
|
183
|
+
// In local mode, skip cloud-only files (GitHub Actions workflows etc.)
|
|
184
|
+
if (gigaclawMode === 'local' && CLOUD_ONLY_PATHS.some(p => outPath === p || outPath.startsWith(p))) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!fs.existsSync(dest)) {
|
|
189
|
+
// File doesn't exist — create it
|
|
190
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
191
|
+
fs.copyFileSync(src, dest);
|
|
192
|
+
created.push(outPath);
|
|
193
|
+
console.log(` Created ${outPath}`);
|
|
194
|
+
} else {
|
|
195
|
+
// File exists — check if template has changed
|
|
196
|
+
const srcContent = fs.readFileSync(src);
|
|
197
|
+
const destContent = fs.readFileSync(dest);
|
|
198
|
+
if (srcContent.equals(destContent)) {
|
|
199
|
+
skipped.push(outPath);
|
|
200
|
+
} else if (!noManaged && isManaged(outPath)) {
|
|
201
|
+
// Managed file differs — auto-update to match package
|
|
202
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
203
|
+
fs.copyFileSync(src, dest);
|
|
204
|
+
updated.push(outPath);
|
|
205
|
+
console.log(` Updated ${outPath}`);
|
|
206
|
+
} else {
|
|
207
|
+
changed.push(outPath);
|
|
208
|
+
console.log(` Skipped ${outPath} (already exists)`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Create package.json if it doesn't exist
|
|
214
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
215
|
+
if (!fs.existsSync(pkgPath)) {
|
|
216
|
+
const dirName = path.basename(cwd);
|
|
217
|
+
const { version } = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
|
|
218
|
+
// Use the exact current version as the minimum — not ^1.0.0 which would
|
|
219
|
+
// resolve to the oldest published version and miss all recent bug fixes.
|
|
220
|
+
const gigaclawDep = version.includes('-') ? version : `^${version}`;
|
|
221
|
+
const pkg = {
|
|
222
|
+
name: dirName,
|
|
223
|
+
private: true,
|
|
224
|
+
scripts: {
|
|
225
|
+
dev: 'next dev --turbopack',
|
|
226
|
+
build: 'next build',
|
|
227
|
+
start: 'next start',
|
|
228
|
+
setup: 'gigaclaw setup',
|
|
229
|
+
'setup-telegram': 'gigaclaw setup-telegram',
|
|
230
|
+
'reset-auth': 'gigaclaw reset-auth',
|
|
231
|
+
},
|
|
232
|
+
dependencies: {
|
|
233
|
+
gigaclaw: gigaclawDep,
|
|
234
|
+
next: '^15.5.12',
|
|
235
|
+
'next-auth': '5.0.0-beta.30',
|
|
236
|
+
'next-themes': '^0.4.0',
|
|
237
|
+
react: '^19.0.0',
|
|
238
|
+
'react-dom': '^19.0.0',
|
|
239
|
+
tailwindcss: '^4.0.0',
|
|
240
|
+
'@tailwindcss/postcss': '^4.0.0',
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
244
|
+
console.log(' Created package.json');
|
|
245
|
+
} else {
|
|
246
|
+
console.log(' Skipped package.json (already exists)');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Create .gitkeep files for empty dirs
|
|
250
|
+
const gitkeepDirs = ['cron', 'triggers', 'logs', 'tmp', 'data'];
|
|
251
|
+
for (const dir of gitkeepDirs) {
|
|
252
|
+
const gitkeep = path.join(cwd, dir, '.gitkeep');
|
|
253
|
+
if (!fs.existsSync(gitkeep)) {
|
|
254
|
+
fs.mkdirSync(path.join(cwd, dir), { recursive: true });
|
|
255
|
+
fs.writeFileSync(gitkeep, '');
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Create default skill activation symlinks
|
|
260
|
+
const defaultSkills = ['browser-tools', 'llm-secrets', 'modify-self'];
|
|
261
|
+
const activeDir = path.join(cwd, 'skills', 'active');
|
|
262
|
+
fs.mkdirSync(activeDir, { recursive: true });
|
|
263
|
+
for (const skill of defaultSkills) {
|
|
264
|
+
const symlink = path.join(activeDir, skill);
|
|
265
|
+
if (!fs.existsSync(symlink)) {
|
|
266
|
+
createDirLink(`../${skill}`, symlink);
|
|
267
|
+
console.log(` Created skills/active/${skill} → ../${skill}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Create .pi/skills → ../skills/active symlink
|
|
272
|
+
const piSkillsLink = path.join(cwd, '.pi', 'skills');
|
|
273
|
+
if (!fs.existsSync(piSkillsLink)) {
|
|
274
|
+
fs.mkdirSync(path.dirname(piSkillsLink), { recursive: true });
|
|
275
|
+
createDirLink('../skills/active', piSkillsLink);
|
|
276
|
+
console.log(' Created .pi/skills → ../skills/active');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Create .claude/skills → ../skills/active symlink
|
|
280
|
+
const claudeSkillsLink = path.join(cwd, '.claude', 'skills');
|
|
281
|
+
if (!fs.existsSync(claudeSkillsLink)) {
|
|
282
|
+
fs.mkdirSync(path.dirname(claudeSkillsLink), { recursive: true });
|
|
283
|
+
createDirLink('../skills/active', claudeSkillsLink);
|
|
284
|
+
console.log(' Created .claude/skills → ../skills/active');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Report updated managed files
|
|
288
|
+
if (updated.length > 0) {
|
|
289
|
+
console.log('\n Updated managed files:');
|
|
290
|
+
for (const file of updated) {
|
|
291
|
+
console.log(` ${file}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Report changed templates
|
|
296
|
+
if (changed.length > 0) {
|
|
297
|
+
console.log('\n Updated templates available:');
|
|
298
|
+
console.log(' These files differ from the current package templates.');
|
|
299
|
+
console.log(' This may be from your edits, or from a gigaclaw update.\n');
|
|
300
|
+
for (const file of changed) {
|
|
301
|
+
console.log(` ${file}`);
|
|
302
|
+
}
|
|
303
|
+
console.log('\n To view differences: npx gigaclaw diff <file>');
|
|
304
|
+
console.log(' To reset to default: npx gigaclaw reset <file>');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Run npm install
|
|
308
|
+
console.log('\nInstalling dependencies...\n');
|
|
309
|
+
// shell:true is required on Windows so npm resolves via PATH (npm.cmd)
|
|
310
|
+
execSync('npm install', { stdio: 'inherit', cwd, shell: true });
|
|
311
|
+
|
|
312
|
+
// Create or update .env with auto-generated infrastructure values
|
|
313
|
+
const envPath = path.join(cwd, '.env');
|
|
314
|
+
const { randomBytes } = await import('crypto');
|
|
315
|
+
const gigaclawPkg = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
|
|
316
|
+
const version = gigaclawPkg.version;
|
|
317
|
+
|
|
318
|
+
if (!fs.existsSync(envPath)) {
|
|
319
|
+
// Seed .env for new projects
|
|
320
|
+
// base64url avoids +, /, and = chars that break dotenv parsing on Windows
|
|
321
|
+
const authSecret = randomBytes(32).toString('base64url');
|
|
322
|
+
const seedEnv = `# gigaclaw Configuration
|
|
323
|
+
# Run "npm run setup" to complete configuration
|
|
324
|
+
|
|
325
|
+
AUTH_SECRET=${authSecret}
|
|
326
|
+
AUTH_TRUST_HOST=true
|
|
327
|
+
GIGACLAW_VERSION=${version}
|
|
328
|
+
`;
|
|
329
|
+
fs.writeFileSync(envPath, seedEnv);
|
|
330
|
+
console.log(` Created .env (AUTH_SECRET, GIGACLAW_VERSION=${version})`);
|
|
331
|
+
} else {
|
|
332
|
+
// Update GIGACLAW_VERSION in existing .env
|
|
333
|
+
try {
|
|
334
|
+
let envContent = fs.readFileSync(envPath, 'utf8');
|
|
335
|
+
if (envContent.match(/^GIGACLAW_VERSION=.*/m)) {
|
|
336
|
+
envContent = envContent.replace(/^GIGACLAW_VERSION=.*/m, `GIGACLAW_VERSION=${version}`);
|
|
337
|
+
} else {
|
|
338
|
+
envContent = envContent.trimEnd() + `\nGIGACLAW_VERSION=${version}\n`;
|
|
339
|
+
}
|
|
340
|
+
fs.writeFileSync(envPath, envContent);
|
|
341
|
+
console.log(` Updated GIGACLAW_VERSION to ${version}`);
|
|
342
|
+
} catch {}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log('\nDone! Run: npm run setup\n');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* List all available template files, or restore a specific one.
|
|
350
|
+
*/
|
|
351
|
+
function reset(filePath) {
|
|
352
|
+
const packageDir = path.join(__dirname, '..');
|
|
353
|
+
const templatesDir = path.join(packageDir, 'templates');
|
|
354
|
+
const cwd = process.cwd();
|
|
355
|
+
|
|
356
|
+
if (!filePath) {
|
|
357
|
+
console.log('\nAvailable template files:\n');
|
|
358
|
+
const files = getTemplateFiles(templatesDir);
|
|
359
|
+
for (const file of files) {
|
|
360
|
+
console.log(` ${destPath(file)}`);
|
|
361
|
+
}
|
|
362
|
+
console.log('\nUsage: gigaclaw reset <file>');
|
|
363
|
+
console.log('Example: gigaclaw reset config/SOUL.md\n');
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const tmplPath = templatePath(filePath, templatesDir);
|
|
368
|
+
const src = path.join(templatesDir, tmplPath);
|
|
369
|
+
const dest = path.join(cwd, filePath);
|
|
370
|
+
|
|
371
|
+
if (!fs.existsSync(src)) {
|
|
372
|
+
console.error(`\nTemplate not found: ${filePath}`);
|
|
373
|
+
console.log('Run "gigaclaw reset" to see available templates.\n');
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (fs.statSync(src).isDirectory()) {
|
|
378
|
+
console.log(`\nRestoring ${filePath}/...\n`);
|
|
379
|
+
copyDirSyncForce(src, dest, tmplPath);
|
|
380
|
+
} else {
|
|
381
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
382
|
+
fs.copyFileSync(src, dest);
|
|
383
|
+
console.log(`\nRestored ${filePath}\n`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Show the diff between a user's file and the package template.
|
|
389
|
+
*/
|
|
390
|
+
function diff(filePath) {
|
|
391
|
+
const packageDir = path.join(__dirname, '..');
|
|
392
|
+
const templatesDir = path.join(packageDir, 'templates');
|
|
393
|
+
const cwd = process.cwd();
|
|
394
|
+
|
|
395
|
+
if (!filePath) {
|
|
396
|
+
// Show all files that differ
|
|
397
|
+
console.log('\nFiles that differ from package templates:\n');
|
|
398
|
+
const files = getTemplateFiles(templatesDir);
|
|
399
|
+
let anyDiff = false;
|
|
400
|
+
for (const file of files) {
|
|
401
|
+
const src = path.join(templatesDir, file);
|
|
402
|
+
const outPath = destPath(file);
|
|
403
|
+
const dest = path.join(cwd, outPath);
|
|
404
|
+
if (fs.existsSync(dest)) {
|
|
405
|
+
const srcContent = fs.readFileSync(src);
|
|
406
|
+
const destContent = fs.readFileSync(dest);
|
|
407
|
+
if (!srcContent.equals(destContent)) {
|
|
408
|
+
console.log(` ${outPath}`);
|
|
409
|
+
anyDiff = true;
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
console.log(` ${outPath} (missing)`);
|
|
413
|
+
anyDiff = true;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!anyDiff) {
|
|
417
|
+
console.log(' All files match package templates.');
|
|
418
|
+
}
|
|
419
|
+
console.log('\nUsage: gigaclaw diff <file>');
|
|
420
|
+
console.log('Example: gigaclaw diff config/SOUL.md\n');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const tmplPath = templatePath(filePath, templatesDir);
|
|
425
|
+
const src = path.join(templatesDir, tmplPath);
|
|
426
|
+
const dest = path.join(cwd, filePath);
|
|
427
|
+
|
|
428
|
+
if (!fs.existsSync(src)) {
|
|
429
|
+
console.error(`\nTemplate not found: ${filePath}`);
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!fs.existsSync(dest)) {
|
|
434
|
+
console.log(`\n${filePath} does not exist in your project.`);
|
|
435
|
+
console.log(`Run "gigaclaw reset ${filePath}" to create it.\n`);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
// Use git diff for nice colored output, fall back to plain diff
|
|
441
|
+
execSync(`git diff --no-index -- "${dest}" "${src}"`, { stdio: 'inherit', shell: true });
|
|
442
|
+
console.log('\nFiles are identical.\n');
|
|
443
|
+
} catch (e) {
|
|
444
|
+
// git diff exits with 1 when files differ (output already printed)
|
|
445
|
+
console.log(`\n To reset: gigaclaw reset ${filePath}\n`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
450
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
451
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
452
|
+
for (const entry of entries) {
|
|
453
|
+
if (EXCLUDED_FILENAMES.includes(entry.name)) continue;
|
|
454
|
+
const srcPath = path.join(src, entry.name);
|
|
455
|
+
const templateRel = templateRelBase
|
|
456
|
+
? path.join(templateRelBase, entry.name)
|
|
457
|
+
: entry.name;
|
|
458
|
+
const outName = path.basename(destPath(templateRel));
|
|
459
|
+
const destFile = path.join(dest, outName);
|
|
460
|
+
if (entry.isDirectory()) {
|
|
461
|
+
copyDirSyncForce(srcPath, destFile, templateRel);
|
|
462
|
+
} else {
|
|
463
|
+
fs.copyFileSync(srcPath, destFile);
|
|
464
|
+
console.log(` Restored ${path.relative(process.cwd(), destFile)}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function setup() {
|
|
470
|
+
const setupScript = path.join(__dirname, '..', 'setup', 'setup.mjs');
|
|
471
|
+
try {
|
|
472
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
|
|
473
|
+
} catch {
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function setupTelegram() {
|
|
479
|
+
const setupScript = path.join(__dirname, '..', 'setup', 'setup-telegram.mjs');
|
|
480
|
+
try {
|
|
481
|
+
execFileSync(process.execPath, [setupScript], { stdio: 'inherit', cwd: process.cwd() });
|
|
482
|
+
} catch {
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function resetAuth() {
|
|
488
|
+
const { randomBytes } = await import('crypto');
|
|
489
|
+
const { updateEnvVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'auth.mjs'));
|
|
490
|
+
|
|
491
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
492
|
+
if (!fs.existsSync(envPath)) {
|
|
493
|
+
console.error('\n No .env file found. Run "npm run setup" first.\n');
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// base64url avoids +, /, and = chars that break dotenv parsing on Windows
|
|
498
|
+
const newSecret = randomBytes(32).toString('base64url');
|
|
499
|
+
updateEnvVariable('AUTH_SECRET', newSecret);
|
|
500
|
+
console.log('\n AUTH_SECRET regenerated.');
|
|
501
|
+
console.log(' All existing sessions have been invalidated.');
|
|
502
|
+
console.log(' Restart your server for the change to take effect.\n');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function upgrade() {
|
|
506
|
+
const cwd = process.cwd();
|
|
507
|
+
const tag = parseUpgradeTarget(args[0]);
|
|
508
|
+
const { confirm, isCancel } = await import('@clack/prompts');
|
|
509
|
+
|
|
510
|
+
// --- Pre-flight: verify this is a gigaclaw project ---
|
|
511
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
512
|
+
if (!fs.existsSync(pkgPath)) {
|
|
513
|
+
console.error('\n Not a gigaclaw project (no package.json found).\n');
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
517
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
518
|
+
if (!deps.gigaclaw) {
|
|
519
|
+
console.error('\n Not a gigaclaw project (gigaclaw not in dependencies).\n');
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Get current installed version
|
|
524
|
+
let currentVersion;
|
|
525
|
+
try {
|
|
526
|
+
const installedPkg = path.join(cwd, 'node_modules', 'gigaclaw', 'package.json');
|
|
527
|
+
currentVersion = JSON.parse(fs.readFileSync(installedPkg, 'utf8')).version;
|
|
528
|
+
} catch {
|
|
529
|
+
currentVersion = 'unknown';
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Resolve target version
|
|
533
|
+
let targetVersion;
|
|
534
|
+
try {
|
|
535
|
+
targetVersion = execSync(`npm view gigaclaw@${tag} version`, { encoding: 'utf8', shell: true }).trim();
|
|
536
|
+
} catch {
|
|
537
|
+
console.error(`\n Could not resolve gigaclaw@${tag}. Check the version/tag and try again.\n`);
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
console.log(`\n gigaclaw ${currentVersion} → ${targetVersion}`);
|
|
542
|
+
|
|
543
|
+
if (currentVersion === targetVersion) {
|
|
544
|
+
console.log(' Already up to date. Nothing to do.\n');
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// --- Save any local changes ---
|
|
549
|
+
const status = execSync('git status --porcelain', { encoding: 'utf8', cwd, shell: true }).trim();
|
|
550
|
+
if (status) {
|
|
551
|
+
console.log('\n You have local changes. Saving them before upgrading...\n');
|
|
552
|
+
try {
|
|
553
|
+
execSync('git add -A && git commit -m "save local changes before gigaclaw upgrade"', { stdio: 'inherit', cwd, shell: true });
|
|
554
|
+
} catch {
|
|
555
|
+
console.error('\n Could not save your local changes. Please try again.\n');
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// --- Pull remote changes ---
|
|
561
|
+
console.log('\n Syncing with remote...\n');
|
|
562
|
+
try {
|
|
563
|
+
execSync('git pull --rebase', { stdio: 'inherit', cwd, shell: true });
|
|
564
|
+
} catch {
|
|
565
|
+
console.error('\n Your local changes conflict with changes on GitHub.');
|
|
566
|
+
console.error(' This means someone (or your bot) changed the same files you did.\n');
|
|
567
|
+
console.error(' To fix this:');
|
|
568
|
+
console.error(' 1. Open the files listed above and look for <<<<<<< markers');
|
|
569
|
+
console.error(' 2. Edit each file to keep the version you want');
|
|
570
|
+
console.error(' 3. Run: git add -A && git rebase --continue');
|
|
571
|
+
console.error(' 4. Then run the upgrade again\n');
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// --- Install ---
|
|
576
|
+
console.log(`\n Installing gigaclaw@${targetVersion}...\n`);
|
|
577
|
+
try {
|
|
578
|
+
execSync(`npm install gigaclaw@${targetVersion}`, { stdio: 'inherit', cwd, shell: true });
|
|
579
|
+
} catch {
|
|
580
|
+
console.error('\n Install failed. Check your internet connection and try again.\n');
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// --- Init (spawn new process to use the NEW version's templates) ---
|
|
585
|
+
console.log('\n Updating project files...\n');
|
|
586
|
+
try {
|
|
587
|
+
execSync('npx gigaclaw init', { stdio: 'inherit', cwd, shell: true });
|
|
588
|
+
} catch {
|
|
589
|
+
console.error('\n Failed to update project files. Try running "npx gigaclaw init" manually.\n');
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// --- Clear .next ---
|
|
594
|
+
try {
|
|
595
|
+
fs.rmSync(path.join(cwd, '.next'), { recursive: true, force: true });
|
|
596
|
+
} catch {}
|
|
597
|
+
|
|
598
|
+
// --- Build ---
|
|
599
|
+
console.log('\n Building...\n');
|
|
600
|
+
try {
|
|
601
|
+
execSync('npm run build', { stdio: 'inherit', cwd, shell: true });
|
|
602
|
+
} catch {
|
|
603
|
+
console.error('\n Build failed. The upgrade has been applied but the project does not build.');
|
|
604
|
+
console.error(' Fix the build errors, then run:\n');
|
|
605
|
+
console.error(` npm run build`);
|
|
606
|
+
console.error(` git add -A && git commit -m "upgrade gigaclaw to ${targetVersion}"`);
|
|
607
|
+
console.error(' git push\n');
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// --- Commit upgrade ---
|
|
612
|
+
const changes = execSync('git status --porcelain', { encoding: 'utf8', cwd, shell: true }).trim();
|
|
613
|
+
if (changes) {
|
|
614
|
+
try {
|
|
615
|
+
execSync('git add -A', { cwd, shell: true });
|
|
616
|
+
execSync(`git commit -m "upgrade gigaclaw to ${targetVersion}"`, { stdio: 'inherit', cwd, shell: true });
|
|
617
|
+
} catch {
|
|
618
|
+
console.error('\n Failed to commit upgrade. Try running manually:');
|
|
619
|
+
console.error(` git add -A && git commit -m "upgrade gigaclaw to ${targetVersion}"\n`);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// --- Push ---
|
|
625
|
+
console.log('\n Pushing to GitHub...\n');
|
|
626
|
+
try {
|
|
627
|
+
execSync('git push', { stdio: 'inherit', cwd, shell: true });
|
|
628
|
+
} catch {
|
|
629
|
+
console.error('\n Could not push to GitHub. Try running "git push" manually.\n');
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// --- Docker restart (only if compose file exists, docker available, and containers running) ---
|
|
634
|
+
const composeFile = path.join(cwd, 'docker-compose.yml');
|
|
635
|
+
if (fs.existsSync(composeFile)) {
|
|
636
|
+
try {
|
|
637
|
+
const running = execSync('docker compose ps --status running -q', { encoding: 'utf8', cwd, shell: true }).trim();
|
|
638
|
+
if (running) {
|
|
639
|
+
console.log(' Restarting Docker containers...\n');
|
|
640
|
+
execSync('docker compose down && docker compose up -d', { stdio: 'inherit', cwd, shell: true });
|
|
641
|
+
}
|
|
642
|
+
} catch {
|
|
643
|
+
// Docker not available or not running — skip
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// --- Summary ---
|
|
648
|
+
console.log(`\n Upgraded gigaclaw ${currentVersion} → ${targetVersion}`);
|
|
649
|
+
console.log(' Done!\n');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Load GH_OWNER and GH_REPO from .env
|
|
654
|
+
*/
|
|
655
|
+
function loadRepoInfo() {
|
|
656
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
657
|
+
if (!fs.existsSync(envPath)) {
|
|
658
|
+
console.error('\n No .env file found. Run "npm run setup" first.\n');
|
|
659
|
+
process.exit(1);
|
|
660
|
+
}
|
|
661
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
662
|
+
const env = {};
|
|
663
|
+
for (const line of content.split('\n')) {
|
|
664
|
+
const match = line.match(/^([^#=]+)=(.*)$/);
|
|
665
|
+
if (match) env[match[1].trim()] = match[2].trim();
|
|
666
|
+
}
|
|
667
|
+
if (!env.GH_OWNER || !env.GH_REPO) {
|
|
668
|
+
console.error('\n GH_OWNER and GH_REPO not found in .env. Run "npm run setup" first.\n');
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
return { owner: env.GH_OWNER, repo: env.GH_REPO };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Read all data from a piped stdin stream.
|
|
676
|
+
* Returns null if stdin is a TTY (interactive terminal).
|
|
677
|
+
*/
|
|
678
|
+
function readStdin() {
|
|
679
|
+
return new Promise((resolve, reject) => {
|
|
680
|
+
if (process.stdin.isTTY) return resolve(null);
|
|
681
|
+
let data = '';
|
|
682
|
+
process.stdin.setEncoding('utf-8');
|
|
683
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
684
|
+
process.stdin.on('end', () => resolve(data.trimEnd() || null));
|
|
685
|
+
process.stdin.on('error', reject);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Prompt for a secret value interactively if not provided as an argument.
|
|
691
|
+
* Supports piped stdin (e.g. echo "val" | gigaclaw set-var KEY).
|
|
692
|
+
*/
|
|
693
|
+
async function promptForValue(key) {
|
|
694
|
+
const stdin = await readStdin();
|
|
695
|
+
if (stdin) return stdin;
|
|
696
|
+
|
|
697
|
+
if (!process.stdin.isTTY) {
|
|
698
|
+
console.error(`\n No value provided for ${key}. Pipe a value or pass it as an argument.\n`);
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const { password, isCancel } = await import('@clack/prompts');
|
|
703
|
+
const value = await password({
|
|
704
|
+
message: `Enter value for ${key}:`,
|
|
705
|
+
validate: (input) => {
|
|
706
|
+
if (!input) return 'Value is required';
|
|
707
|
+
},
|
|
708
|
+
});
|
|
709
|
+
if (isCancel(value)) {
|
|
710
|
+
console.log('\nCancelled.\n');
|
|
711
|
+
process.exit(0);
|
|
712
|
+
}
|
|
713
|
+
return value;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function setAgentSecret(key, value) {
|
|
717
|
+
if (!key) {
|
|
718
|
+
console.error('\n Usage: gigaclaw set-agent-secret <KEY> [VALUE]\n');
|
|
719
|
+
console.error(' Example: gigaclaw set-agent-secret ANTHROPIC_API_KEY\n');
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (!value) value = await promptForValue(key);
|
|
724
|
+
|
|
725
|
+
const { owner, repo } = loadRepoInfo();
|
|
726
|
+
const prefixedName = `AGENT_${key}`;
|
|
727
|
+
|
|
728
|
+
const { setSecret } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
|
|
729
|
+
const { updateEnvVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'auth.mjs'));
|
|
730
|
+
|
|
731
|
+
const result = await setSecret(owner, repo, prefixedName, value);
|
|
732
|
+
if (result.success) {
|
|
733
|
+
console.log(`\n Set GitHub secret: ${prefixedName}`);
|
|
734
|
+
updateEnvVariable(key, value);
|
|
735
|
+
console.log(` Updated .env: ${key}`);
|
|
736
|
+
console.log('');
|
|
737
|
+
} else {
|
|
738
|
+
console.error(`\n Failed to set ${prefixedName}: ${result.error}\n`);
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
async function setAgentLlmSecret(key, value) {
|
|
744
|
+
if (!key) {
|
|
745
|
+
console.error('\n Usage: gigaclaw set-agent-llm-secret <KEY> [VALUE]\n');
|
|
746
|
+
console.error(' Example: gigaclaw set-agent-llm-secret BRAVE_API_KEY\n');
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!value) value = await promptForValue(key);
|
|
751
|
+
|
|
752
|
+
const { owner, repo } = loadRepoInfo();
|
|
753
|
+
const prefixedName = `AGENT_LLM_${key}`;
|
|
754
|
+
|
|
755
|
+
const { setSecret } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
|
|
756
|
+
|
|
757
|
+
const result = await setSecret(owner, repo, prefixedName, value);
|
|
758
|
+
if (result.success) {
|
|
759
|
+
console.log(`\n Set GitHub secret: ${prefixedName}\n`);
|
|
760
|
+
} else {
|
|
761
|
+
console.error(`\n Failed to set ${prefixedName}: ${result.error}\n`);
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async function setVar(key, value) {
|
|
767
|
+
if (!key) {
|
|
768
|
+
console.error('\n Usage: gigaclaw set-var <KEY> [VALUE]\n');
|
|
769
|
+
console.error(' Example: gigaclaw set-var LLM_MODEL claude-sonnet-4-5-20250929\n');
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (!value) value = await promptForValue(key);
|
|
774
|
+
|
|
775
|
+
const { owner, repo } = loadRepoInfo();
|
|
776
|
+
|
|
777
|
+
const { setVariable } = await import(path.join(__dirname, '..', 'setup', 'lib', 'github.mjs'));
|
|
778
|
+
|
|
779
|
+
const result = await setVariable(owner, repo, key, value);
|
|
780
|
+
if (result.success) {
|
|
781
|
+
console.log(`\n Set GitHub variable: ${key}\n`);
|
|
782
|
+
} else {
|
|
783
|
+
console.error(`\n Failed to set ${key}: ${result.error}\n`);
|
|
784
|
+
process.exit(1);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
switch (command) {
|
|
789
|
+
case 'init':
|
|
790
|
+
await init();
|
|
791
|
+
break;
|
|
792
|
+
case 'setup':
|
|
793
|
+
setup();
|
|
794
|
+
break;
|
|
795
|
+
case 'setup-telegram':
|
|
796
|
+
setupTelegram();
|
|
797
|
+
break;
|
|
798
|
+
case 'reset-auth':
|
|
799
|
+
await resetAuth();
|
|
800
|
+
break;
|
|
801
|
+
case 'reset':
|
|
802
|
+
reset(args[0]);
|
|
803
|
+
break;
|
|
804
|
+
case 'diff':
|
|
805
|
+
diff(args[0]);
|
|
806
|
+
break;
|
|
807
|
+
case 'upgrade':
|
|
808
|
+
case 'update':
|
|
809
|
+
await upgrade();
|
|
810
|
+
break;
|
|
811
|
+
case 'set-agent-secret':
|
|
812
|
+
await setAgentSecret(args[0], args[1]);
|
|
813
|
+
break;
|
|
814
|
+
case 'set-agent-llm-secret':
|
|
815
|
+
await setAgentLlmSecret(args[0], args[1]);
|
|
816
|
+
break;
|
|
817
|
+
case 'set-var':
|
|
818
|
+
await setVar(args[0], args[1]);
|
|
819
|
+
break;
|
|
820
|
+
default:
|
|
821
|
+
printUsage();
|
|
822
|
+
process.exit(command ? 1 : 0);
|
|
823
|
+
}
|