stella-coder 5.0.1 → 5.1.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/.github/workflows/deploy.yml +58 -0
- package/next.config.mjs +1 -0
- package/package.json +1 -1
- package/stella-cli/autonomous-agent.mjs +476 -0
- package/stella-cli/banner.mjs +1 -1
- package/stella-cli/index.mjs +208 -1
- package/stella-cli/tools.mjs +7 -6
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Deploy to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
|
|
24
|
+
- name: Setup Node.js
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: "20"
|
|
28
|
+
|
|
29
|
+
- name: Install pnpm
|
|
30
|
+
uses: pnpm/action-setup@v4
|
|
31
|
+
with:
|
|
32
|
+
version: 9
|
|
33
|
+
|
|
34
|
+
- name: Setup Pages
|
|
35
|
+
id: pages
|
|
36
|
+
uses: actions/configure-pages@v5
|
|
37
|
+
|
|
38
|
+
- name: Install dependencies
|
|
39
|
+
run: pnpm install
|
|
40
|
+
|
|
41
|
+
- name: Build with Next.js
|
|
42
|
+
run: pnpm run build
|
|
43
|
+
|
|
44
|
+
- name: Upload artifact
|
|
45
|
+
uses: actions/upload-pages-artifact@v3
|
|
46
|
+
with:
|
|
47
|
+
path: ./out
|
|
48
|
+
|
|
49
|
+
deploy:
|
|
50
|
+
environment:
|
|
51
|
+
name: github-pages
|
|
52
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
needs: build
|
|
55
|
+
steps:
|
|
56
|
+
- name: Deploy to GitHub Pages
|
|
57
|
+
id: deployment
|
|
58
|
+
uses: actions/deploy-pages@v4
|
package/next.config.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stella-coder",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Stella Coder 5.0 — AI coding agent with Telegram bot, huge context, TDD, Git ecosystem, presentations, computer control, smart home, Office automation, and antivirus",
|
|
6
6
|
"main": "stella-cli/index.mjs",
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, appendFileSync } from "fs"
|
|
2
|
+
import { join } from "path"
|
|
3
|
+
import { homedir } from "os"
|
|
4
|
+
import { execSync } from "child_process"
|
|
5
|
+
|
|
6
|
+
const STELLA_DIR = join(homedir(), ".stella")
|
|
7
|
+
const AUTO_DIR = join(STELLA_DIR, "autonomous")
|
|
8
|
+
const MEMORY_FILE = join(AUTO_DIR, "memory.json")
|
|
9
|
+
const TASKS_FILE = join(AUTO_DIR, "tasks.json")
|
|
10
|
+
const LOG_FILE = join(AUTO_DIR, "activity.log")
|
|
11
|
+
const DASHBOARD_FILE = join(AUTO_DIR, "dashboard.html")
|
|
12
|
+
const STATE_FILE = join(AUTO_DIR, "state.json")
|
|
13
|
+
|
|
14
|
+
function ensureDir() {
|
|
15
|
+
if (!existsSync(AUTO_DIR)) mkdirSync(AUTO_DIR, { recursive: true })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function loadJSON(file, fallback) {
|
|
19
|
+
try { return JSON.parse(readFileSync(file, "utf-8")) } catch { return fallback }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function saveJSON(file, data) {
|
|
23
|
+
ensureDir()
|
|
24
|
+
writeFileSync(file, JSON.stringify(data, null, 2))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function logEntry(action, details) {
|
|
28
|
+
ensureDir()
|
|
29
|
+
const entry = `[${new Date().toISOString()}] ${action}: ${details}\n`
|
|
30
|
+
appendFileSync(LOG_FILE, entry)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shellRun(cmd) {
|
|
34
|
+
try {
|
|
35
|
+
execSync(cmd, { timeout: 60000, stdio: "pipe", shell: true })
|
|
36
|
+
return true
|
|
37
|
+
} catch { return false }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class AutonomousAgent {
|
|
41
|
+
constructor() {
|
|
42
|
+
ensureDir()
|
|
43
|
+
this.memory = loadJSON(MEMORY_FILE, { projects: [], accounts: [], videos: [], earnings: 0, lessons: [] })
|
|
44
|
+
this.tasks = loadJSON(TASKS_FILE, { queue: [], completed: [], failed: [] })
|
|
45
|
+
this.state = loadJSON(STATE_FILE, { running: false, startedAt: null, goal: "", pid: null, iterations: 0 })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
save() {
|
|
49
|
+
saveJSON(MEMORY_FILE, this.memory)
|
|
50
|
+
saveJSON(TASKS_FILE, this.tasks)
|
|
51
|
+
saveJSON(STATE_FILE, this.state)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
planGoal(goal) {
|
|
55
|
+
const plan = {
|
|
56
|
+
id: Date.now().toString(36),
|
|
57
|
+
goal,
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
59
|
+
steps: [],
|
|
60
|
+
status: "planning"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const g = goal.toLowerCase()
|
|
64
|
+
|
|
65
|
+
if (g.includes("приложен") || g.includes("app") || g.includes("заработ") || g.includes("$") || g.includes("монетиз")) {
|
|
66
|
+
plan.steps = [
|
|
67
|
+
{ id: "research", name: "Research market & competitors", status: "pending", subtasks: [
|
|
68
|
+
"Search for trending app ideas and niches",
|
|
69
|
+
"Analyze top competitors in the space",
|
|
70
|
+
"Define monetization strategy (ads, subscriptions, donations)"
|
|
71
|
+
], results: [] },
|
|
72
|
+
{ id: "architect", name: "Architecture & design", status: "pending", subtasks: [
|
|
73
|
+
"Choose tech stack (Next.js + Tailwind + Supabase)",
|
|
74
|
+
"Design database schema",
|
|
75
|
+
"Create API structure"
|
|
76
|
+
], results: [] },
|
|
77
|
+
{ id: "build", name: "Build the application", status: "pending", subtasks: [
|
|
78
|
+
"Create project with package.json, config files",
|
|
79
|
+
"Build frontend with UI components",
|
|
80
|
+
"Build backend API routes",
|
|
81
|
+
"Add Stripe/Paddle payment integration",
|
|
82
|
+
"Add donation button (Buy Me a Coffee / Ko-fi)",
|
|
83
|
+
"Add user authentication"
|
|
84
|
+
], results: [] },
|
|
85
|
+
{ id: "deploy", name: "Deploy to production", status: "pending", subtasks: [
|
|
86
|
+
"Initialize git repo, commit all files",
|
|
87
|
+
"Push to GitHub",
|
|
88
|
+
"Deploy frontend to Vercel",
|
|
89
|
+
"Deploy backend to Railway/Render",
|
|
90
|
+
"Configure custom domain if needed"
|
|
91
|
+
], results: [] },
|
|
92
|
+
{ id: "accounts", name: "Create promo accounts", status: "pending", subtasks: [
|
|
93
|
+
"Create YouTube channel for the project",
|
|
94
|
+
"Create TikTok account",
|
|
95
|
+
"Create Twitter/X account",
|
|
96
|
+
"Create Telegram channel",
|
|
97
|
+
"Create Product Hunt listing"
|
|
98
|
+
], results: [] },
|
|
99
|
+
{ id: "content", name: "Create promotional content", status: "pending", subtasks: [
|
|
100
|
+
"Write launch blog post",
|
|
101
|
+
"Create demo video script",
|
|
102
|
+
"Write Reddit posts for r/SideProject, r/webdev",
|
|
103
|
+
"Create Hacker News Show HN post",
|
|
104
|
+
"Write Product Hunt description"
|
|
105
|
+
], results: [] },
|
|
106
|
+
{ id: "dashboard", name: "Build management dashboard", status: "pending", subtasks: [
|
|
107
|
+
"Create HTML dashboard page",
|
|
108
|
+
"Add analytics tracking",
|
|
109
|
+
"Add financial metrics view",
|
|
110
|
+
"Add user management"
|
|
111
|
+
], results: [] },
|
|
112
|
+
{ id: "optimize", name: "Growth & optimization", status: "pending", subtasks: [
|
|
113
|
+
"SEO optimization",
|
|
114
|
+
"Performance audit",
|
|
115
|
+
"Add analytics (Google Analytics / Plausible)",
|
|
116
|
+
"Set up email list"
|
|
117
|
+
], results: [] }
|
|
118
|
+
]
|
|
119
|
+
} else if (g.includes("сайт") || g.includes("website") || g.includes("лендинг")) {
|
|
120
|
+
plan.steps = [
|
|
121
|
+
{ id: "design", name: "Design the website", status: "pending", subtasks: [
|
|
122
|
+
"Create wireframe and layout",
|
|
123
|
+
"Design UI with modern dark theme",
|
|
124
|
+
"Write compelling copy"
|
|
125
|
+
], results: [] },
|
|
126
|
+
{ id: "build", name: "Build the website", status: "pending", subtasks: [
|
|
127
|
+
"Create HTML/CSS/JS files",
|
|
128
|
+
"Add responsive design",
|
|
129
|
+
"Add animations and interactions"
|
|
130
|
+
], results: [] },
|
|
131
|
+
{ id: "seo", name: "SEO & performance", status: "pending", subtasks: [
|
|
132
|
+
"Add meta tags and Open Graph",
|
|
133
|
+
"Optimize images and loading",
|
|
134
|
+
"Add structured data"
|
|
135
|
+
], results: [] },
|
|
136
|
+
{ id: "deploy", name: "Deploy the website", status: "pending", subtasks: [
|
|
137
|
+
"Push to GitHub",
|
|
138
|
+
"Enable GitHub Pages or deploy to Vercel",
|
|
139
|
+
"Configure domain"
|
|
140
|
+
], results: [] },
|
|
141
|
+
{ id: "promo", name: "Promote the website", status: "pending", subtasks: [
|
|
142
|
+
"Share on social media",
|
|
143
|
+
"Submit to directories",
|
|
144
|
+
"Run ad campaigns"
|
|
145
|
+
], results: [] }
|
|
146
|
+
]
|
|
147
|
+
} else {
|
|
148
|
+
plan.steps = [
|
|
149
|
+
{ id: "analyze", name: "Analyze the task", status: "pending", subtasks: [
|
|
150
|
+
"Break down requirements",
|
|
151
|
+
"Identify needed resources",
|
|
152
|
+
"Create execution plan"
|
|
153
|
+
], results: [] },
|
|
154
|
+
{ id: "execute", name: "Execute the plan", status: "pending", subtasks: [
|
|
155
|
+
"Implement step by step",
|
|
156
|
+
"Test at each stage",
|
|
157
|
+
"Handle errors and retry"
|
|
158
|
+
], results: [] },
|
|
159
|
+
{ id: "deliver", name: "Deliver results", status: "pending", subtasks: [
|
|
160
|
+
"Document everything",
|
|
161
|
+
"Create summary report",
|
|
162
|
+
"Generate dashboard"
|
|
163
|
+
], results: [] }
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.tasks.queue.push(plan)
|
|
168
|
+
this.state.goal = goal
|
|
169
|
+
this.save()
|
|
170
|
+
logEntry("PLAN_CREATED", goal)
|
|
171
|
+
return plan
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async executeSubtask(subtask, goal, apiCall) {
|
|
175
|
+
const prompt = `You are an autonomous coding agent. Complete this task: "${subtask}"
|
|
176
|
+
Context: Goal is "${goal}"
|
|
177
|
+
|
|
178
|
+
You have access to:
|
|
179
|
+
- File system (create/edit files)
|
|
180
|
+
- npm/pnpm (install packages)
|
|
181
|
+
- Git (commit, push)
|
|
182
|
+
- Web search (find information)
|
|
183
|
+
- Shell commands (run anything)
|
|
184
|
+
|
|
185
|
+
DO NOT just describe what to do. ACTUALLY DO IT. Create REAL files, run REAL commands, make REAL progress.
|
|
186
|
+
|
|
187
|
+
Return a JSON object:
|
|
188
|
+
{
|
|
189
|
+
"action": "what was done",
|
|
190
|
+
"filesCreated": ["list of files created"],
|
|
191
|
+
"commandsRun": ["commands executed"],
|
|
192
|
+
"result": "description of result",
|
|
193
|
+
"nextSteps": ["what to do next"]
|
|
194
|
+
}`
|
|
195
|
+
|
|
196
|
+
const response = await apiCall(prompt)
|
|
197
|
+
let parsed
|
|
198
|
+
try {
|
|
199
|
+
parsed = typeof response === "string" ? JSON.parse(response) : response
|
|
200
|
+
} catch {
|
|
201
|
+
parsed = { action: "completed", result: response, filesCreated: [], commandsRun: [] }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (parsed.filesCreated) {
|
|
205
|
+
for (const f of parsed.filesCreated) {
|
|
206
|
+
this.memory.projects.push({ file: f, created: new Date().toISOString(), goal, subtask })
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
logEntry("SUBTASK_DONE", `${subtask}: ${parsed.result || "done"}`)
|
|
210
|
+
this.save()
|
|
211
|
+
return parsed
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async runIteration(apiCall, onProgress) {
|
|
215
|
+
const plan = this.tasks.queue.find(p => p.status !== "completed" && p.status !== "failed")
|
|
216
|
+
if (!plan) return null
|
|
217
|
+
|
|
218
|
+
plan.status = "running"
|
|
219
|
+
const step = plan.steps.find(s => s.status === "pending")
|
|
220
|
+
if (!step) {
|
|
221
|
+
const hasFailed = plan.steps.some(s => s.status === "failed")
|
|
222
|
+
plan.status = hasFailed ? "failed" : "completed"
|
|
223
|
+
this.save()
|
|
224
|
+
logEntry("PLAN_DONE", `${plan.goal}: ${plan.status}`)
|
|
225
|
+
return { type: "plan_done", plan }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
step.status = "running"
|
|
229
|
+
this.save()
|
|
230
|
+
if (onProgress) onProgress(`Executing: ${step.name}`)
|
|
231
|
+
logEntry("STEP_START", step.name)
|
|
232
|
+
|
|
233
|
+
const stepResults = []
|
|
234
|
+
let failed = false
|
|
235
|
+
|
|
236
|
+
for (const subtask of step.subtasks) {
|
|
237
|
+
try {
|
|
238
|
+
if (onProgress) onProgress(` → ${subtask}`)
|
|
239
|
+
const result = await this.executeSubtask(subtask, plan.goal, apiCall)
|
|
240
|
+
stepResults.push(result)
|
|
241
|
+
} catch (err) {
|
|
242
|
+
logEntry("SUBTASK_FAIL", `${subtask}: ${err.message}`)
|
|
243
|
+
stepResults.push({ error: err.message })
|
|
244
|
+
failed = true
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
step.status = failed ? "failed" : "completed"
|
|
249
|
+
step.results = stepResults
|
|
250
|
+
this.save()
|
|
251
|
+
logEntry("STEP_DONE", `${step.name}: ${step.status}`)
|
|
252
|
+
return { type: "step_done", step, plan }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async start(goal, apiCall, onProgress) {
|
|
256
|
+
this.state.running = true
|
|
257
|
+
this.state.startedAt = new Date().toISOString()
|
|
258
|
+
this.state.goal = goal
|
|
259
|
+
this.state.iterations = 0
|
|
260
|
+
this.save()
|
|
261
|
+
|
|
262
|
+
const plan = this.planGoal(goal)
|
|
263
|
+
logEntry("AGENT_START", goal)
|
|
264
|
+
|
|
265
|
+
while (this.state.running) {
|
|
266
|
+
this.state.iterations++
|
|
267
|
+
const result = await this.runIteration(apiCall, onProgress)
|
|
268
|
+
|
|
269
|
+
if (!result) {
|
|
270
|
+
this.state.running = false
|
|
271
|
+
this.save()
|
|
272
|
+
logEntry("AGENT_DONE", "All plans completed!")
|
|
273
|
+
if (onProgress) onProgress("ALL_DONE")
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (result.type === "plan_done") {
|
|
278
|
+
if (onProgress) onProgress(`Plan completed: ${result.plan.goal}`)
|
|
279
|
+
} else if (result.type === "step_done") {
|
|
280
|
+
if (onProgress) onProgress(`Step done: ${result.step.name}`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
await new Promise(r => setTimeout(r, 500))
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.state.running = false
|
|
287
|
+
this.save()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
stop() {
|
|
291
|
+
this.state.running = false
|
|
292
|
+
this.save()
|
|
293
|
+
logEntry("AGENT_STOPPED", "Manually stopped")
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
getStatus() {
|
|
297
|
+
const plan = this.tasks.queue.find(p => p.status === "running" || p.status === "planning")
|
|
298
|
+
return {
|
|
299
|
+
running: this.state.running,
|
|
300
|
+
goal: this.state.goal,
|
|
301
|
+
iterations: this.state.iterations,
|
|
302
|
+
startedAt: this.state.startedAt,
|
|
303
|
+
currentPlan: plan ? {
|
|
304
|
+
goal: plan.goal,
|
|
305
|
+
status: plan.status,
|
|
306
|
+
steps: plan.steps.map(s => ({
|
|
307
|
+
name: s.name,
|
|
308
|
+
status: s.status,
|
|
309
|
+
total: s.subtasks.length,
|
|
310
|
+
done: (s.results || []).filter(r => !r.error).length
|
|
311
|
+
}))
|
|
312
|
+
} : null,
|
|
313
|
+
completedPlans: this.tasks.queue.filter(p => p.status === "completed").length,
|
|
314
|
+
failedPlans: this.tasks.queue.filter(p => p.status === "failed").length,
|
|
315
|
+
totalFiles: this.memory.projects.length,
|
|
316
|
+
totalEarnings: this.memory.earnings,
|
|
317
|
+
accounts: this.memory.accounts.length
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
generateDashboard() {
|
|
322
|
+
const completedPlans = this.tasks.queue.filter(p => p.status === "completed")
|
|
323
|
+
const failedPlans = this.tasks.queue.filter(p => p.status === "failed")
|
|
324
|
+
const activePlans = this.tasks.queue.filter(p => p.status === "running" || p.status === "planning")
|
|
325
|
+
const allFiles = [...new Set(this.memory.projects.map(p => p.file))]
|
|
326
|
+
const logs = existsSync(LOG_FILE) ? readFileSync(LOG_FILE, "utf-8").split("\n").filter(Boolean).slice(-100) : []
|
|
327
|
+
|
|
328
|
+
const html = `<!DOCTYPE html>
|
|
329
|
+
<html lang="ru">
|
|
330
|
+
<head>
|
|
331
|
+
<meta charset="UTF-8">
|
|
332
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
333
|
+
<title>Stella Autonomous Dashboard</title>
|
|
334
|
+
<style>
|
|
335
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
336
|
+
body{background:#0a0a0f;color:#f8fafc;font-family:'Courier New',monospace;min-height:100vh}
|
|
337
|
+
.hdr{background:linear-gradient(135deg,#0f0f1a,#1a0a2e);padding:2rem 3rem;border-bottom:2px solid #22c55e;display:flex;justify-content:space-between;align-items:center}
|
|
338
|
+
.hdr h1{font-size:2rem;color:#22c55e}
|
|
339
|
+
.hdr .status{display:flex;align-items:center;gap:8px}
|
|
340
|
+
.hdr .dot{width:12px;height:12px;border-radius:50%;background:${this.state.running ? "#22c55e" : "#ef4444"};animation:pulse 1.5s infinite}
|
|
341
|
+
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
|
342
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:1rem;padding:2rem 3rem}
|
|
343
|
+
.card{background:#111118;border:1px solid #22c55e33;border-radius:12px;padding:1.5rem;text-align:center;transition:border-color .3s}
|
|
344
|
+
.card:hover{border-color:#22c55e}
|
|
345
|
+
.card .num{font-size:2.5rem;color:#22c55e;font-weight:bold}
|
|
346
|
+
.card .lbl{color:#94a3b8;margin-top:.3rem;font-size:.85rem}
|
|
347
|
+
.sec{padding:1.5rem 3rem}
|
|
348
|
+
.sec h2{color:#22c55e;font-size:1.3rem;margin-bottom:1rem;display:flex;align-items:center;gap:8px}
|
|
349
|
+
.plan-card{background:#111118;border:1px solid #333;border-radius:12px;padding:1.5rem;margin-bottom:1rem}
|
|
350
|
+
.plan-card h3{color:#e2e8f0;font-size:1.1rem}
|
|
351
|
+
.plan-card .meta{color:#64748b;font-size:.8rem;margin:.5rem 0}
|
|
352
|
+
.step-row{display:flex;align-items:center;gap:10px;padding:.6rem 0;border-bottom:1px solid #1a1a2e}
|
|
353
|
+
.step-row:last-child{border:none}
|
|
354
|
+
.badge{padding:3px 10px;border-radius:6px;font-size:.7rem;font-weight:bold;text-transform:uppercase}
|
|
355
|
+
.badge.completed{background:#22c55e22;color:#22c55e}
|
|
356
|
+
.badge.failed{background:#ef444422;color:#ef4444}
|
|
357
|
+
.badge.running{background:#eab30822;color:#eab308}
|
|
358
|
+
.badge.pending{background:#64748b22;color:#64748b}
|
|
359
|
+
.badge.planning{background:#3b82f622;color:#3b82f6}
|
|
360
|
+
.files-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:.5rem}
|
|
361
|
+
.file-item{background:#16161f;padding:.5rem 1rem;border-radius:6px;font-size:.85rem;color:#22c55e;display:flex;align-items:center;gap:6px}
|
|
362
|
+
.log-box{background:#111118;border-radius:12px;padding:1rem;max-height:400px;overflow-y:auto;font-size:.78rem;color:#94a3b8;line-height:1.6}
|
|
363
|
+
.log-box div{padding:2px 0;border-bottom:1px solid #111}
|
|
364
|
+
.log-box .ts{color:#64748b}
|
|
365
|
+
.footer{text-align:center;padding:2rem;color:#475569;border-top:1px solid #1a1a2e;margin-top:2rem}
|
|
366
|
+
.empty{text-align:center;padding:3rem;color:#475569;font-size:1.1rem}
|
|
367
|
+
</style>
|
|
368
|
+
</head>
|
|
369
|
+
<body>
|
|
370
|
+
<div class="hdr">
|
|
371
|
+
<div>
|
|
372
|
+
<h1>🤖 Stella Autonomous Agent</h1>
|
|
373
|
+
<p style="color:#94a3b8;margin-top:.3rem">Self-driving coding machine — powered by codex alex</p>
|
|
374
|
+
</div>
|
|
375
|
+
<div class="status">
|
|
376
|
+
<div class="dot"></div>
|
|
377
|
+
<span style="color:${this.state.running ? "#22c55e" : "#ef4444"};font-weight:bold">${this.state.running ? "RUNNING" : "IDLE"}</span>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<div class="grid">
|
|
382
|
+
<div class="card"><div class="num">${completedPlans.length}</div><div class="lbl">Plans Done</div></div>
|
|
383
|
+
<div class="card"><div class="num">${activePlans.length}</div><div class="lbl">Active Plans</div></div>
|
|
384
|
+
<div class="card"><div class="num">${failedPlans.length}</div><div class="lbl">Failed</div></div>
|
|
385
|
+
<div class="card"><div class="num">${allFiles.length}</div><div class="lbl">Files Created</div></div>
|
|
386
|
+
<div class="card"><div class="num">$${this.memory.earnings.toFixed(2)}</div><div class="lbl">Earnings</div></div>
|
|
387
|
+
<div class="card"><div class="num">${this.memory.accounts.length}</div><div class="lbl">Accounts</div></div>
|
|
388
|
+
<div class="card"><div class="num">${this.state.iterations}</div><div class="lbl">Iterations</div></div>
|
|
389
|
+
<div class="card"><div class="num">${this.memory.videos.length}</div><div class="lbl">Videos</div></div>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
${activePlans.length > 0 ? `
|
|
393
|
+
<div class="sec">
|
|
394
|
+
<h2>⚡ Active Plans</h2>
|
|
395
|
+
${activePlans.map(p => `
|
|
396
|
+
<div class="plan-card">
|
|
397
|
+
<h3>${p.goal}</h3>
|
|
398
|
+
<div class="meta">Started: ${new Date(p.createdAt).toLocaleString()}</div>
|
|
399
|
+
${p.steps.map(s => `
|
|
400
|
+
<div class="step-row">
|
|
401
|
+
<span class="badge ${s.status}">${s.status}</span>
|
|
402
|
+
<span>${s.name}</span>
|
|
403
|
+
<span style="margin-left:auto;color:#64748b">${(s.results||[]).filter(r=>!r.error).length}/${s.subtasks.length}</span>
|
|
404
|
+
</div>
|
|
405
|
+
`).join("")}
|
|
406
|
+
</div>
|
|
407
|
+
`).join("")}
|
|
408
|
+
</div>` : ""}
|
|
409
|
+
|
|
410
|
+
${completedPlans.length > 0 ? `
|
|
411
|
+
<div class="sec">
|
|
412
|
+
<h2>✅ Completed Plans</h2>
|
|
413
|
+
${completedPlans.map(p => `
|
|
414
|
+
<div class="plan-card">
|
|
415
|
+
<h3>${p.goal}</h3>
|
|
416
|
+
<div class="meta">Completed: ${new Date(p.createdAt).toLocaleString()}</div>
|
|
417
|
+
${p.steps.map(s => `
|
|
418
|
+
<div class="step-row">
|
|
419
|
+
<span class="badge completed">done</span>
|
|
420
|
+
<span>${s.name}</span>
|
|
421
|
+
</div>
|
|
422
|
+
`).join("")}
|
|
423
|
+
</div>
|
|
424
|
+
`).join("")}
|
|
425
|
+
</div>` : ""}
|
|
426
|
+
|
|
427
|
+
${failedPlans.length > 0 ? `
|
|
428
|
+
<div class="sec">
|
|
429
|
+
<h2>❌ Failed Plans</h2>
|
|
430
|
+
${failedPlans.map(p => `
|
|
431
|
+
<div class="plan-card">
|
|
432
|
+
<h3>${p.goal}</h3>
|
|
433
|
+
${p.steps.filter(s=>s.status==="failed").map(s => `
|
|
434
|
+
<div class="step-row">
|
|
435
|
+
<span class="badge failed">failed</span>
|
|
436
|
+
<span>${s.name}</span>
|
|
437
|
+
</div>
|
|
438
|
+
`).join("")}
|
|
439
|
+
</div>
|
|
440
|
+
`).join("")}
|
|
441
|
+
</div>` : ""}
|
|
442
|
+
|
|
443
|
+
<div class="sec">
|
|
444
|
+
<h2>📁 All Created Files</h2>
|
|
445
|
+
${allFiles.length > 0 ? `
|
|
446
|
+
<div class="files-grid">
|
|
447
|
+
${allFiles.map(f => `<div class="file-item">📄 ${f}</div>`).join("")}
|
|
448
|
+
</div>
|
|
449
|
+
` : '<div class="empty">No files created yet</div>'}
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<div class="sec">
|
|
453
|
+
<h2>📝 Activity Log (last 100 entries)</h2>
|
|
454
|
+
<div class="log-box">
|
|
455
|
+
${logs.length > 0 ? logs.map(l => {
|
|
456
|
+
const parts = l.match(/\[(.*?)\] (\w+): (.*)/)
|
|
457
|
+
if (parts) return `<div><span class="ts">${parts[1]}</span> <span style="color:#22c55e">${parts[2]}</span> ${parts[3]}</div>`
|
|
458
|
+
return `<div>${l}</div>`
|
|
459
|
+
}).join("") : '<div>No activity yet</div>'}
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<div class="footer">
|
|
464
|
+
<p>Stella Coder 5.1 — Autonomous Agent Dashboard</p>
|
|
465
|
+
<p>Generated: ${new Date().toLocaleString()} · Goal: ${this.state.goal || "None"}</p>
|
|
466
|
+
</div>
|
|
467
|
+
</body>
|
|
468
|
+
</html>`
|
|
469
|
+
|
|
470
|
+
ensureDir()
|
|
471
|
+
writeFileSync(DASHBOARD_FILE, html)
|
|
472
|
+
return DASHBOARD_FILE
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export default AutonomousAgent
|
package/stella-cli/banner.mjs
CHANGED
|
@@ -30,7 +30,7 @@ export function printBanner({ model, cwd, version }) {
|
|
|
30
30
|
console.log(
|
|
31
31
|
box(
|
|
32
32
|
[
|
|
33
|
-
violet("✻") + " Добро пожаловать в " + bold(violet("Stella Coder 5.
|
|
33
|
+
violet("✻") + " Добро пожаловать в " + bold(violet("Stella Coder 5.1")) + "!",
|
|
34
34
|
"",
|
|
35
35
|
dim(" модель: ") + blue(model),
|
|
36
36
|
dim(" папка: ") + gray(cwd),
|
package/stella-cli/index.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { runSubagent, listSubagents, parseAgentCommand, SUBAGENTS } from "./subagents.mjs"
|
|
22
22
|
import { mcp, MCP_COMMANDS } from "./mcp.mjs"
|
|
23
23
|
import { generatePresentation, createPresentationFromTopic, AVAILABLE_THEMES, exportToPDF } from "./presentations.mjs"
|
|
24
|
+
import { AutonomousAgent } from "./autonomous-agent.mjs"
|
|
24
25
|
import {
|
|
25
26
|
buildRepoMap, buildProjectContext, compressContext,
|
|
26
27
|
loadSpec, generateSpecTemplate,
|
|
@@ -40,7 +41,7 @@ import {
|
|
|
40
41
|
generateAdminCode, listAuthorizedUsers,
|
|
41
42
|
} from "./telegram-bot.mjs"
|
|
42
43
|
|
|
43
|
-
const VERSION = "5.0
|
|
44
|
+
const VERSION = "5.1.0"
|
|
44
45
|
const CONFIG_DIR = path.join(os.homedir(), ".stella")
|
|
45
46
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json")
|
|
46
47
|
const HISTORY_PATH = path.join(CONFIG_DIR, "history.json")
|
|
@@ -642,6 +643,13 @@ const COMMANDS = [
|
|
|
642
643
|
["/tg-code", "сгенерировать код для продажи доступа"],
|
|
643
644
|
["/tg-users", "показать кто подключён"],
|
|
644
645
|
|
|
646
|
+
// Autonomous Agent
|
|
647
|
+
["/auto", "запустить автономный режим (цель)"],
|
|
648
|
+
["/auto-stop", "остановить автономный агента"],
|
|
649
|
+
["/auto-status", "статус автономного агента"],
|
|
650
|
+
["/auto-dashboard", "создать dashboard с результатами"],
|
|
651
|
+
["/auto-history", "история выполненных задач"],
|
|
652
|
+
|
|
645
653
|
// Управление компьютером
|
|
646
654
|
["/open", "открыть приложение/файл/URL"],
|
|
647
655
|
["/app", "запустить приложение"],
|
|
@@ -766,6 +774,7 @@ async function handleCommand(line) {
|
|
|
766
774
|
["🧠 Coding Brain", ["/brain", "/brain-map", "/brain-compress", "/spec", "/tdd", "/lint-auto", "/format-auto", "/typecheck-auto", "/test-auto", "/fix-all"]],
|
|
767
775
|
["📱 Telegram", ["/tg", "/tg-stop", "/tg-notify", "/tg-sessions", "/tg-verify", "/tg-code", "/tg-users"]],
|
|
768
776
|
["🔀 Git Экосистема", ["/git-eco", "/git-pr", "/git-merge-auto"]],
|
|
777
|
+
["🤖 Autonomous Agent", ["/auto", "/auto-stop", "/auto-status", "/auto-dashboard", "/auto-history"]],
|
|
769
778
|
["⚙️ Настройки", ["/help", "/model", "/clear", "/compact", "/cost", "/context", "/config", "/version", "/login", "/newkey", "/doctor", "/sessions", "/color", "/lang", "/shortcut"]],
|
|
770
779
|
]
|
|
771
780
|
for (const [cat, cmds] of categories) {
|
|
@@ -3387,6 +3396,204 @@ async function handleCommand(line) {
|
|
|
3387
3396
|
return
|
|
3388
3397
|
}
|
|
3389
3398
|
|
|
3399
|
+
// ═══════════════════════════════════════════════════
|
|
3400
|
+
// AUTONOMOUS AGENT
|
|
3401
|
+
// ═══════════════════════════════════════════════════
|
|
3402
|
+
case "/auto": {
|
|
3403
|
+
if (!arg) {
|
|
3404
|
+
console.log(dim("\n Использование: /auto <цель>\n"))
|
|
3405
|
+
console.log(dim(" Пример: /auto создай приложение и заработай $10\n"))
|
|
3406
|
+
console.log(dim(" Агент будет работать автономно пока не выполнит цель.\n"))
|
|
3407
|
+
return
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
const goal = arg.trim()
|
|
3411
|
+
const agent = new AutonomousAgent()
|
|
3412
|
+
|
|
3413
|
+
console.log()
|
|
3414
|
+
console.log(box([
|
|
3415
|
+
violet("🤖 AUTONOMOUS AGENT"),
|
|
3416
|
+
"",
|
|
3417
|
+
white("Цель: ") + cyan(goal),
|
|
3418
|
+
"",
|
|
3419
|
+
dim("Агент создаст план и начнёт автономное выполнение."),
|
|
3420
|
+
dim("Каждый шаг будет выполняться автоматически."),
|
|
3421
|
+
dim("Используй /auto-stop для остановки."),
|
|
3422
|
+
dim("Используй /auto-status для проверки прогресса."),
|
|
3423
|
+
], { color: violet, padding: 2 }))
|
|
3424
|
+
console.log()
|
|
3425
|
+
|
|
3426
|
+
const confirm = await question(" " + green("Начать автономное выполнение? (y/n) › "))
|
|
3427
|
+
if (confirm.trim().toLowerCase() !== "y" && confirm.trim().toLowerCase() !== "д") {
|
|
3428
|
+
console.log(dim(" Отменено\n"))
|
|
3429
|
+
return
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
console.log()
|
|
3433
|
+
startSpinner("Составляю план выполнения")
|
|
3434
|
+
const plan = agent.planGoal(goal)
|
|
3435
|
+
stopSpinner()
|
|
3436
|
+
|
|
3437
|
+
console.log(box([
|
|
3438
|
+
green("✓ План создан!"),
|
|
3439
|
+
"",
|
|
3440
|
+
...plan.steps.map((s, i) => dim(` ${i + 1}. `) + white(s.name)),
|
|
3441
|
+
"",
|
|
3442
|
+
dim(`Всего шагов: ${plan.steps.length}`),
|
|
3443
|
+
], { title: "План", color: green, padding: 2 }))
|
|
3444
|
+
console.log()
|
|
3445
|
+
|
|
3446
|
+
const apiCall = async (prompt) => {
|
|
3447
|
+
const response = await queryAI(prompt, apiKey, state.model)
|
|
3448
|
+
return response
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
console.log(dim(" Запускаю автономный режим...\n"))
|
|
3452
|
+
|
|
3453
|
+
agent.state.running = true
|
|
3454
|
+
agent.state.startedAt = new Date().toISOString()
|
|
3455
|
+
agent.state.goal = goal
|
|
3456
|
+
agent.save()
|
|
3457
|
+
|
|
3458
|
+
let stepCount = 0
|
|
3459
|
+
const runAuto = async () => {
|
|
3460
|
+
while (agent.state.running) {
|
|
3461
|
+
const result = await agent.runIteration(apiCall, (msg) => {
|
|
3462
|
+
if (msg === "ALL_DONE") {
|
|
3463
|
+
console.log(green("\n ✅ ВСЕ ЗАДАЧИ ВЫПОЛНЕНЫ!\n"))
|
|
3464
|
+
agent.generateDashboard()
|
|
3465
|
+
console.log(dim(" Dashboard: ~/.stella/autonomous/dashboard.html\n"))
|
|
3466
|
+
return
|
|
3467
|
+
}
|
|
3468
|
+
console.log(dim(" ") + violet("→") + " " + white(msg))
|
|
3469
|
+
})
|
|
3470
|
+
|
|
3471
|
+
if (!result) {
|
|
3472
|
+
agent.state.running = false
|
|
3473
|
+
agent.save()
|
|
3474
|
+
console.log(green("\n ✅ Все планы выполнены!\n"))
|
|
3475
|
+
agent.generateDashboard()
|
|
3476
|
+
console.log(dim(" Dashboard: ~/.stella/autonomous/dashboard.html\n"))
|
|
3477
|
+
break
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
stepCount++
|
|
3481
|
+
if (stepCount % 5 === 0) {
|
|
3482
|
+
console.log(dim(` [${stepCount} шагов выполнено]`))
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
await new Promise(r => setTimeout(r, 1000))
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
runAuto().catch(err => {
|
|
3490
|
+
console.log(red(`\n ✗ Ошибка: ${err.message}\n`))
|
|
3491
|
+
agent.state.running = false
|
|
3492
|
+
agent.save()
|
|
3493
|
+
})
|
|
3494
|
+
|
|
3495
|
+
return
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
case "/auto-stop": {
|
|
3499
|
+
console.log()
|
|
3500
|
+
const agent = new AutonomousAgent()
|
|
3501
|
+
if (!agent.state.running) {
|
|
3502
|
+
console.log(dim(" Агент не запущен\n"))
|
|
3503
|
+
return
|
|
3504
|
+
}
|
|
3505
|
+
agent.stop()
|
|
3506
|
+
console.log(green(" ✓ Автономный агент остановлен\n"))
|
|
3507
|
+
console.log(dim(` Выполнено шагов: ${agent.state.iterations}`))
|
|
3508
|
+
console.log(dim(` Создано файлов: ${agent.memory.projects.length}`))
|
|
3509
|
+
console.log()
|
|
3510
|
+
return
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
case "/auto-status": {
|
|
3514
|
+
console.log()
|
|
3515
|
+
const agent = new AutonomousAgent()
|
|
3516
|
+
const status = agent.getStatus()
|
|
3517
|
+
|
|
3518
|
+
if (!status.running && !status.currentPlan) {
|
|
3519
|
+
console.log(dim(" Агент не запущен. Используй /auto <цель> для запуска.\n"))
|
|
3520
|
+
return
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
const items = [
|
|
3524
|
+
dim("Статус: ") + (status.running ? green("● RUNNINg") : red("○ STOPPED")),
|
|
3525
|
+
dim("Цель: ") + white(status.goal || "Нет"),
|
|
3526
|
+
dim("Итераций: ") + cyan(String(status.iterations)),
|
|
3527
|
+
dim("Начато: ") + white(status.startedAt ? new Date(status.startedAt).toLocaleString() : "—"),
|
|
3528
|
+
"",
|
|
3529
|
+
dim("Планов выполнено: ") + green(String(status.completedPlans)),
|
|
3530
|
+
dim("Планов.failed: ") + red(String(status.failedPlans)),
|
|
3531
|
+
dim("Файлов создано: ") + cyan(String(status.totalFiles)),
|
|
3532
|
+
dim("Заработано: ") + green("$" + status.totalEarnings.toFixed(2)),
|
|
3533
|
+
dim("Аккаунтов: ") + cyan(String(status.accounts)),
|
|
3534
|
+
]
|
|
3535
|
+
|
|
3536
|
+
if (status.currentPlan) {
|
|
3537
|
+
items.push("", violet("Текущий план: ") + white(status.currentPlan.goal))
|
|
3538
|
+
items.push(dim("Прогресс:"))
|
|
3539
|
+
for (const step of status.currentPlan.steps) {
|
|
3540
|
+
const icon = step.status === "completed" ? green("✓") : step.status === "running" ? yellow("●") : step.status === "failed" ? red("✗") : dim("○")
|
|
3541
|
+
items.push(` ${icon} ${step.name} ${dim(`(${step.done}/${step.total})`)}`)
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
console.log(box(items, { title: "🤖 Autonomous Agent Status", color: violet, padding: 2 }))
|
|
3546
|
+
console.log()
|
|
3547
|
+
return
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
case "/auto-dashboard": {
|
|
3551
|
+
console.log()
|
|
3552
|
+
const agent = new AutonomousAgent()
|
|
3553
|
+
startSpinner("Создаю dashboard")
|
|
3554
|
+
const path = agent.generateDashboard()
|
|
3555
|
+
stopSpinner()
|
|
3556
|
+
console.log(green(` ✓ Dashboard создан: ${path}`))
|
|
3557
|
+
console.log(dim(" Откройте в браузере для просмотра результатов\n"))
|
|
3558
|
+
|
|
3559
|
+
const openIt = await question(" " + green("Открыть dashboard? (y/n) › "))
|
|
3560
|
+
if (openIt.trim().toLowerCase() === "y" || openIt.trim().toLowerCase() === "д") {
|
|
3561
|
+
try {
|
|
3562
|
+
execSync(`start "" "${path}"`, { shell: "cmd.exe", stdio: "pipe" })
|
|
3563
|
+
} catch (e) {
|
|
3564
|
+
console.log(yellow(" ⚠ Не удалось открыть автоматически\n"))
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
console.log()
|
|
3568
|
+
return
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3571
|
+
case "/auto-history": {
|
|
3572
|
+
console.log()
|
|
3573
|
+
const agent = new AutonomousAgent()
|
|
3574
|
+
const allPlans = agent.tasks.queue
|
|
3575
|
+
|
|
3576
|
+
if (allPlans.length === 0) {
|
|
3577
|
+
console.log(dim(" Нет истории. Используй /auto <цель> для запуска.\n"))
|
|
3578
|
+
return
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
console.log(box([
|
|
3582
|
+
...allPlans.map(p => {
|
|
3583
|
+
const icon = p.status === "completed" ? green("✓") : p.status === "failed" ? red("✗") : yellow("●")
|
|
3584
|
+
return `${icon} ${white(p.goal)} ${dim(`(${p.status})`)}`
|
|
3585
|
+
}),
|
|
3586
|
+
], { title: "История автономных задач", color: violet, padding: 2 }))
|
|
3587
|
+
console.log()
|
|
3588
|
+
|
|
3589
|
+
// Show memory stats
|
|
3590
|
+
console.log(dim(` Всего проектов: ${agent.memory.projects.length}`))
|
|
3591
|
+
console.log(dim(` Аккаунтов: ${agent.memory.accounts.length}`))
|
|
3592
|
+
console.log(dim(` Уроков: ${agent.memory.lessons.length}`))
|
|
3593
|
+
console.log()
|
|
3594
|
+
return
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3390
3597
|
default:
|
|
3391
3598
|
console.log(dim(`\n Неизвестная команда: ${cmd}. Смотри /help\n`))
|
|
3392
3599
|
}
|
package/stella-cli/tools.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { tool } from "ai"
|
|
|
2
2
|
import { z } from "zod"
|
|
3
3
|
import fs from "node:fs"
|
|
4
4
|
import path from "node:path"
|
|
5
|
+
import os from "node:os"
|
|
5
6
|
import { execSync } from "node:child_process"
|
|
6
7
|
|
|
7
8
|
const MAX_OUTPUT = 30000
|
|
@@ -949,12 +950,12 @@ export function createTools(permissions) {
|
|
|
949
950
|
platform: process.platform,
|
|
950
951
|
arch: process.arch,
|
|
951
952
|
nodeVersion: process.version,
|
|
952
|
-
hostname:
|
|
953
|
-
username:
|
|
954
|
-
cpus:
|
|
955
|
-
totalMemory: `${(
|
|
956
|
-
freeMemory: `${(
|
|
957
|
-
uptime: `${Math.floor(
|
|
953
|
+
hostname: os.hostname(),
|
|
954
|
+
username: os.userInfo().username,
|
|
955
|
+
cpus: os.cpus().length,
|
|
956
|
+
totalMemory: `${(os.totalmem() / 1e9).toFixed(1)} GB`,
|
|
957
|
+
freeMemory: `${(os.freemem() / 1e9).toFixed(1)} GB`,
|
|
958
|
+
uptime: `${Math.floor(os.uptime() / 3600)}h ${Math.floor((os.uptime() % 3600) / 60)}m`,
|
|
958
959
|
}
|
|
959
960
|
return info
|
|
960
961
|
} catch (e) {
|