neoagent 2.1.18-beta.35 → 2.1.18-beta.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/index.md +23 -42
- package/extensions/chrome-browser/popup.js +30 -57
- package/package.json +11 -5
- package/server/catalog_sources/store-bundles/skills/productivity/catbox-upload-skill/SKILL.md +99 -0
- package/server/catalog_sources/store-bundles/skills/productivity/catbox-upload-skill/scripts/upload.sh +155 -0
- package/server/http/middleware.js +12 -20
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +25047 -25097
- package/server/services/ai/engine.js +95 -67
- package/server/services/ai/tools.js +17 -26
- package/server/services/manager.js +107 -116
- package/server/services/skills/store_bundles.js +1 -0
- package/docs/.vitepress/config.mts +0 -102
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<p align="center"><strong>Your agent. Your server. Your rules.</strong></p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-
|
|
6
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-20+-5fa04e?style=flat-square&logo=node.js&logoColor=white" alt="Node.js"></a>
|
|
7
7
|
<a href="https://sqlite.org"><img src="https://img.shields.io/badge/SQLite-WAL-003b57?style=flat-square&logo=sqlite&logoColor=white" alt="SQLite"></a>
|
|
8
8
|
<a href="https://flutter.dev"><img src="https://img.shields.io/badge/Flutter-web%20%2B%20android-02569B?style=flat-square&logo=flutter&logoColor=white" alt="Flutter"></a>
|
|
9
9
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-a855f7?style=flat-square" alt="License"></a>
|
package/docs/getting-started.md
CHANGED
|
@@ -4,7 +4,7 @@ NeoAgent installs as a Node CLI and runs a self-hosted server with a bundled Flu
|
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
|
-
- Node.js
|
|
7
|
+
- Node.js 20 or newer.
|
|
8
8
|
- A reachable server URL if you want OAuth callbacks, mobile access, or messaging webhooks.
|
|
9
9
|
- At least one hosted AI provider API key, unless you only use local Ollama.
|
|
10
10
|
- Android Studio or a Flutter Android toolchain if you build the Android client yourself.
|
package/docs/index.md
CHANGED
|
@@ -1,47 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
slug: /
|
|
3
|
+
title: NeoAgent
|
|
4
|
+
sidebar_label: Overview
|
|
5
|
+
---
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
name: NeoAgent
|
|
6
|
-
text: Self-hosted proactive AI agent
|
|
7
|
-
tagline: Run your own server, keep credentials server-side, and operate browser, Android, recordings, schedules, integrations, memory, MCP, and messaging from one Flutter UI.
|
|
8
|
-
actions:
|
|
9
|
-
- theme: brand
|
|
10
|
-
text: Get Started
|
|
11
|
-
link: /getting-started
|
|
12
|
-
- theme: alt
|
|
13
|
-
text: Capabilities
|
|
14
|
-
link: /capabilities
|
|
15
|
-
- theme: alt
|
|
16
|
-
text: Why NeoAgent
|
|
17
|
-
link: /why-neoagent
|
|
7
|
+
# NeoAgent
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
link: /capabilities#android-control
|
|
23
|
-
linkText: Android Control
|
|
24
|
-
- title: Recordings
|
|
25
|
-
details: Capture web, Android, and wearable audio as sessions with transcripts, searchable segments, playback, retry, cleanup, and AI-generated insights.
|
|
26
|
-
link: /capabilities#recordings
|
|
27
|
-
linkText: Recordings
|
|
28
|
-
- title: Proactive Automation
|
|
29
|
-
details: Create recurring tasks and one-time runs that can use browser, files, CLI, memory, MCP, integrations, subagents, health summaries, and messaging delivery.
|
|
30
|
-
link: /automation
|
|
31
|
-
linkText: Automation
|
|
32
|
-
- title: Official Integrations
|
|
33
|
-
details: Use OAuth-backed Google Workspace, Microsoft 365, Notion, Slack, and Figma tools instead of brittle browser automation where possible.
|
|
34
|
-
link: /integrations
|
|
35
|
-
linkText: Integrations
|
|
36
|
-
- title: Server-Side Secrets
|
|
37
|
-
details: Keep AI provider keys, OAuth client secrets, Telnyx tokens, runtime settings, and deployment controls on the NeoAgent server.
|
|
38
|
-
link: /configuration
|
|
39
|
-
linkText: Configuration
|
|
40
|
-
- title: Recovery Path
|
|
41
|
-
details: Operate self-hosted installs with status, logs, release channels, update, fix, runtime paths, and the remote-server log caveat.
|
|
42
|
-
link: /operations
|
|
43
|
-
linkText: Operations
|
|
44
|
-
---
|
|
9
|
+
NeoAgent is a self-hosted proactive AI agent with a bundled Flutter client for web and Android. It runs on your server, keeps credentials server-side, and gives you an operator UI for chat, runs, logs, scheduler tasks, skills, integrations, MCP, memory, Android control, recordings, Health Connect data, wearables, and settings.
|
|
10
|
+
|
|
11
|
+
It is designed for people who want a focused personal automation server rather than a broad gateway platform. NeoAgent can run scheduled tasks, control a browser, operate a server-attached Android emulator or device, manage files, remember long-term context, connect to hosted AI providers or local Ollama, search recordings, read synced health summaries, and send results through Telegram, Discord, WhatsApp, or Telnyx Voice.
|
|
45
12
|
|
|
46
13
|
## Quick Start
|
|
47
14
|
|
|
@@ -52,7 +19,21 @@ neoagent install
|
|
|
52
19
|
|
|
53
20
|
Open the server URL, sign in, configure providers and messaging, then create a scheduled task or chat run.
|
|
54
21
|
|
|
55
|
-
##
|
|
22
|
+
## What NeoAgent Does
|
|
23
|
+
|
|
24
|
+
| Area | Capability |
|
|
25
|
+
|---|---|
|
|
26
|
+
| AI providers | OpenAI, Anthropic, xAI, Google, MiniMax Code, and local Ollama |
|
|
27
|
+
| Operator UI | Chat, live runs, logs, scheduler, skills, integrations, MCP, memory, devices, recordings, health, wearables, settings |
|
|
28
|
+
| Automation | Recurring scheduled tasks, one-time runs, browser control, file access, CLI skills, subagents, and messaging delivery |
|
|
29
|
+
| Android control | AI control of a server-attached Android emulator or device: screenshots, UI dumps, taps, typing, intents, APK installs, and ADB shell |
|
|
30
|
+
| Recordings | Web, Android, and wearable audio sessions with transcript search and AI insights |
|
|
31
|
+
| Integrations | Google Workspace, Notion, Microsoft 365, Slack, Figma, and remote MCP servers |
|
|
32
|
+
| Messaging | Telegram, Discord, WhatsApp text/media, and Telnyx Voice calls |
|
|
33
|
+
| Outputs | Artifacts, Grok image generation, vision analysis, markdown tables, and Mermaid graphs |
|
|
34
|
+
| Recovery | `neoagent status`, `neoagent logs`, `neoagent update`, release channels, and `neoagent fix` |
|
|
35
|
+
|
|
36
|
+
## Main Paths
|
|
56
37
|
|
|
57
38
|
| Need | Start here |
|
|
58
39
|
|---|---|
|
|
@@ -10,6 +10,9 @@ const flowDescriptionEl = document.querySelector('#flowDescription');
|
|
|
10
10
|
const primaryActionEl = document.querySelector('#primaryAction');
|
|
11
11
|
const secondaryActionEl = document.querySelector('#secondaryAction');
|
|
12
12
|
const openAppEl = document.querySelector('#openApp');
|
|
13
|
+
const disconnectEl = document.querySelector('#disconnect');
|
|
14
|
+
const checkUpdateEl = document.querySelector('#checkUpdate');
|
|
15
|
+
const downloadEl = document.querySelector('#download');
|
|
13
16
|
|
|
14
17
|
const STATUS_LABELS = {
|
|
15
18
|
connected: 'Connected',
|
|
@@ -186,68 +189,38 @@ async function refresh() {
|
|
|
186
189
|
updateFlow();
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
setMessage('');
|
|
201
|
-
await runAction(secondaryActionEl.dataset.action);
|
|
202
|
-
} catch (error) {
|
|
203
|
-
setMessage(error.message, 'error');
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
openAppEl.addEventListener('click', async () => {
|
|
208
|
-
try {
|
|
209
|
-
setMessage('');
|
|
210
|
-
await runAction('openApp');
|
|
211
|
-
} catch (error) {
|
|
212
|
-
setMessage(error.message, 'error');
|
|
213
|
-
}
|
|
214
|
-
});
|
|
192
|
+
function bindAsyncClick(element, handler) {
|
|
193
|
+
element.addEventListener('click', async () => {
|
|
194
|
+
try {
|
|
195
|
+
setMessage('');
|
|
196
|
+
await handler();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
setMessage(error.message, 'error');
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
215
202
|
|
|
216
203
|
serverUrlEl.addEventListener('input', updateFlow);
|
|
217
204
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
setMessage(error.message, 'error');
|
|
226
|
-
}
|
|
205
|
+
bindAsyncClick(primaryActionEl, () => runAction(primaryActionEl.dataset.action));
|
|
206
|
+
bindAsyncClick(secondaryActionEl, () => runAction(secondaryActionEl.dataset.action));
|
|
207
|
+
bindAsyncClick(openAppEl, () => runAction('openApp'));
|
|
208
|
+
bindAsyncClick(disconnectEl, async () => {
|
|
209
|
+
await send('disconnect');
|
|
210
|
+
await refresh();
|
|
211
|
+
setMessage('Disconnected.', 'success');
|
|
227
212
|
});
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
: `Current version ${result.currentVersion} is up to date.`,
|
|
237
|
-
result.updateAvailable ? '' : 'success',
|
|
238
|
-
);
|
|
239
|
-
} catch (error) {
|
|
240
|
-
setMessage(error.message, 'error');
|
|
241
|
-
}
|
|
213
|
+
bindAsyncClick(checkUpdateEl, async () => {
|
|
214
|
+
const result = await send('checkForUpdates', { serverUrl: effectiveServerUrl() });
|
|
215
|
+
setMessage(
|
|
216
|
+
result.updateAvailable
|
|
217
|
+
? `Update available: ${result.currentVersion} -> ${result.latestVersion}.`
|
|
218
|
+
: `Current version ${result.currentVersion} is up to date.`,
|
|
219
|
+
result.updateAvailable ? '' : 'success',
|
|
220
|
+
);
|
|
242
221
|
});
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
setMessage('');
|
|
247
|
-
await send('openDownload', { serverUrl: effectiveServerUrl() });
|
|
248
|
-
} catch (error) {
|
|
249
|
-
setMessage(error.message, 'error');
|
|
250
|
-
}
|
|
222
|
+
bindAsyncClick(downloadEl, async () => {
|
|
223
|
+
await send('openDownload', { serverUrl: effectiveServerUrl() });
|
|
251
224
|
});
|
|
252
225
|
|
|
253
226
|
chrome.runtime.onMessage.addListener((message) => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neoagent",
|
|
3
|
-
"version": "2.1.18-beta.
|
|
3
|
+
"version": "2.1.18-beta.37",
|
|
4
4
|
"description": "Proactive personal AI agent with no limits",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "server/index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
7
10
|
"bin": {
|
|
8
11
|
"neoagent": "bin/neoagent.js"
|
|
9
12
|
},
|
|
@@ -28,9 +31,9 @@
|
|
|
28
31
|
"dev:stack": "./dev/stack.sh",
|
|
29
32
|
"dev:build": "./dev/build.sh",
|
|
30
33
|
"dev:test": "./dev/test.sh",
|
|
31
|
-
"docs:dev": "
|
|
32
|
-
"docs:build": "
|
|
33
|
-
"docs:preview": "
|
|
34
|
+
"docs:dev": "docusaurus start",
|
|
35
|
+
"docs:build": "docusaurus build",
|
|
36
|
+
"docs:preview": "docusaurus serve build",
|
|
34
37
|
"flutter:run:web": "cd flutter_app && flutter run -d chrome",
|
|
35
38
|
"flutter:build:web": "cd flutter_app && flutter build web --output ../server/public --dart-define=NEOAGENT_BACKEND_URL=${NEOAGENT_BACKEND_URL:-}",
|
|
36
39
|
"manage": "node bin/neoagent.js",
|
|
@@ -83,6 +86,9 @@
|
|
|
83
86
|
"undici": "^6.24.0"
|
|
84
87
|
},
|
|
85
88
|
"devDependencies": {
|
|
86
|
-
"
|
|
89
|
+
"@docusaurus/core": "3.8.1",
|
|
90
|
+
"@docusaurus/preset-classic": "3.8.1",
|
|
91
|
+
"react": "18.3.1",
|
|
92
|
+
"react-dom": "18.3.1"
|
|
87
93
|
}
|
|
88
94
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Catbox & Litterbox File Uploader
|
|
3
|
+
description: Upload local files to Catbox for permanent sharing or Litterbox for temporary sharing. Use when you need a public link for logs, screenshots, archives, media, or other local artifacts.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
author: Microck
|
|
6
|
+
upstream: https://github.com/Microck/opendots-microck/tree/main/skills/catbox-upload-skill
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Catbox & Litterbox File Uploader
|
|
10
|
+
|
|
11
|
+
Use this skill when the user wants a direct public URL for a local file.
|
|
12
|
+
|
|
13
|
+
- Prefer `litterbox` for temporary sharing of logs, builds, screenshots, and one-off artifacts.
|
|
14
|
+
- Prefer `catbox` for permanent hosting.
|
|
15
|
+
- Return the final URL plainly, and for `litterbox` also mention the selected expiry window.
|
|
16
|
+
|
|
17
|
+
## Command
|
|
18
|
+
|
|
19
|
+
Run the bundled uploader script:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bash ~/.neoagent/agent-data/skills/productivity-catbox-upload-skill/scripts/upload.sh /path/to/file
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Arguments
|
|
26
|
+
|
|
27
|
+
- `file_path` (required): local file to upload
|
|
28
|
+
- `--service litterbox|catbox` (optional): defaults to `litterbox`
|
|
29
|
+
- `--time 1h|12h|24h|72h` (optional): only used for `litterbox`, defaults to `24h`
|
|
30
|
+
- `--userhash HASH` (optional): Catbox account hash for uploads tied to a Catbox account
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
Temporary upload with the default 24-hour expiry:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bash ~/.neoagent/agent-data/skills/productivity-catbox-upload-skill/scripts/upload.sh ./logs/server.log
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Temporary upload that expires after 1 hour:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bash ~/.neoagent/agent-data/skills/productivity-catbox-upload-skill/scripts/upload.sh ./build/output.zip --time 1h
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Permanent upload to Catbox:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bash ~/.neoagent/agent-data/skills/productivity-catbox-upload-skill/scripts/upload.sh ./artifacts/screenshot.png --service catbox
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Permanent upload associated with a Catbox account:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bash ~/.neoagent/agent-data/skills/productivity-catbox-upload-skill/scripts/upload.sh ./artifacts/screenshot.png --service catbox --userhash YOUR_HASH
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Expected Output
|
|
59
|
+
|
|
60
|
+
The script prints exactly one URL on success, for example:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
https://litterbox.catbox.moe/abc123.png
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
or:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
https://files.catbox.moe/abc123.png
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Presenting Results
|
|
73
|
+
|
|
74
|
+
When the upload succeeds:
|
|
75
|
+
|
|
76
|
+
- show the returned URL clearly
|
|
77
|
+
- mention whether it is temporary or permanent
|
|
78
|
+
- if using `litterbox`, include the expiration window
|
|
79
|
+
|
|
80
|
+
Example response:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
File uploaded successfully.
|
|
84
|
+
|
|
85
|
+
Link: https://litterbox.catbox.moe/abc123.png
|
|
86
|
+
Expires in: 24 hours
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Limits
|
|
90
|
+
|
|
91
|
+
- `litterbox`: up to 1 GB, temporary, supports `1h`, `12h`, `24h`, and `72h`
|
|
92
|
+
- `catbox`: up to 200 MB, permanent
|
|
93
|
+
|
|
94
|
+
## Troubleshooting
|
|
95
|
+
|
|
96
|
+
- If the file does not exist or is unreadable, verify the path first.
|
|
97
|
+
- If the upload fails, surface the exact error from the script.
|
|
98
|
+
- If `curl` is missing, install it before retrying.
|
|
99
|
+
- If Catbox rejects the file, the file may exceed the service limit or violate Catbox content rules.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat >&2 <<'EOF'
|
|
6
|
+
Usage: upload.sh <file_path> [--service litterbox|catbox] [--time 1h|12h|24h|72h] [--userhash HASH]
|
|
7
|
+
|
|
8
|
+
Options:
|
|
9
|
+
--service Upload target. Defaults to litterbox.
|
|
10
|
+
--time Litterbox retention window. Defaults to 24h.
|
|
11
|
+
--userhash Optional Catbox account hash for uploads associated with an account.
|
|
12
|
+
-h, --help Show this help text.
|
|
13
|
+
|
|
14
|
+
Limits:
|
|
15
|
+
litterbox: 1 GB
|
|
16
|
+
catbox: 200 MB
|
|
17
|
+
EOF
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fail() {
|
|
21
|
+
printf 'Error: %s\n' "$1" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
require_value() {
|
|
26
|
+
local option="$1"
|
|
27
|
+
local value="${2-}"
|
|
28
|
+
[[ -n "$value" ]] || fail "$option requires a value"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
SERVICE="litterbox"
|
|
32
|
+
TIME="24h"
|
|
33
|
+
USERHASH=""
|
|
34
|
+
FILE_PATH=""
|
|
35
|
+
|
|
36
|
+
while [[ $# -gt 0 ]]; do
|
|
37
|
+
case "$1" in
|
|
38
|
+
--service)
|
|
39
|
+
require_value "$1" "${2-}"
|
|
40
|
+
SERVICE="$2"
|
|
41
|
+
shift 2
|
|
42
|
+
;;
|
|
43
|
+
--time)
|
|
44
|
+
require_value "$1" "${2-}"
|
|
45
|
+
TIME="$2"
|
|
46
|
+
shift 2
|
|
47
|
+
;;
|
|
48
|
+
--userhash)
|
|
49
|
+
require_value "$1" "${2-}"
|
|
50
|
+
USERHASH="$2"
|
|
51
|
+
shift 2
|
|
52
|
+
;;
|
|
53
|
+
-h|--help)
|
|
54
|
+
usage
|
|
55
|
+
exit 0
|
|
56
|
+
;;
|
|
57
|
+
--)
|
|
58
|
+
shift
|
|
59
|
+
break
|
|
60
|
+
;;
|
|
61
|
+
-*)
|
|
62
|
+
usage
|
|
63
|
+
fail "unknown option: $1"
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
[[ -z "$FILE_PATH" ]] || fail "only one file path is supported"
|
|
67
|
+
FILE_PATH="$1"
|
|
68
|
+
shift
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
if [[ $# -gt 0 ]]; then
|
|
74
|
+
[[ -z "$FILE_PATH" ]] || fail "only one file path is supported"
|
|
75
|
+
FILE_PATH="$1"
|
|
76
|
+
shift
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
[[ $# -eq 0 ]] || fail "only one file path is supported"
|
|
80
|
+
|
|
81
|
+
[[ -n "$FILE_PATH" ]] || {
|
|
82
|
+
usage
|
|
83
|
+
fail "file path is required"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
command -v curl >/dev/null 2>&1 || fail "curl is not installed"
|
|
87
|
+
[[ -f "$FILE_PATH" ]] || fail "file not found: $FILE_PATH"
|
|
88
|
+
[[ -r "$FILE_PATH" ]] || fail "file is not readable: $FILE_PATH"
|
|
89
|
+
|
|
90
|
+
case "$SERVICE" in
|
|
91
|
+
litterbox|catbox)
|
|
92
|
+
;;
|
|
93
|
+
*)
|
|
94
|
+
fail "service must be either 'litterbox' or 'catbox'"
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
case "$TIME" in
|
|
99
|
+
1h|12h|24h|72h)
|
|
100
|
+
;;
|
|
101
|
+
*)
|
|
102
|
+
fail "time must be one of: 1h, 12h, 24h, 72h"
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
|
|
106
|
+
FILE_SIZE_BYTES="$(wc -c < "$FILE_PATH" | tr -d '[:space:]')"
|
|
107
|
+
LITTERBOX_LIMIT_BYTES=1073741824
|
|
108
|
+
CATBOX_LIMIT_BYTES=209715200
|
|
109
|
+
|
|
110
|
+
if [[ "$SERVICE" == "litterbox" ]] && (( FILE_SIZE_BYTES > LITTERBOX_LIMIT_BYTES )); then
|
|
111
|
+
fail "litterbox uploads are limited to 1 GB"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
if [[ "$SERVICE" == "catbox" ]] && (( FILE_SIZE_BYTES > CATBOX_LIMIT_BYTES )); then
|
|
115
|
+
fail "catbox uploads are limited to 200 MB"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
CURL_FILE_PATH="${FILE_PATH//;/\\;}"
|
|
119
|
+
|
|
120
|
+
if [[ "$SERVICE" == "litterbox" ]]; then
|
|
121
|
+
ENDPOINT="https://litterbox.catbox.moe/resources/internals/api.php"
|
|
122
|
+
RESPONSE="$(
|
|
123
|
+
curl --silent --show-error --fail \
|
|
124
|
+
--user-agent 'NeoAgent catbox-upload-skill/1.0' \
|
|
125
|
+
-F 'reqtype=fileupload' \
|
|
126
|
+
-F "time=$TIME" \
|
|
127
|
+
-F "fileToUpload=@$CURL_FILE_PATH" \
|
|
128
|
+
"$ENDPOINT"
|
|
129
|
+
)"
|
|
130
|
+
else
|
|
131
|
+
ENDPOINT="https://catbox.moe/user/api.php"
|
|
132
|
+
if [[ -n "$USERHASH" ]]; then
|
|
133
|
+
RESPONSE="$(
|
|
134
|
+
curl --silent --show-error --fail \
|
|
135
|
+
--user-agent 'NeoAgent catbox-upload-skill/1.0' \
|
|
136
|
+
-F 'reqtype=fileupload' \
|
|
137
|
+
-F "userhash=$USERHASH" \
|
|
138
|
+
-F "fileToUpload=@$CURL_FILE_PATH" \
|
|
139
|
+
"$ENDPOINT"
|
|
140
|
+
)"
|
|
141
|
+
else
|
|
142
|
+
RESPONSE="$(
|
|
143
|
+
curl --silent --show-error --fail \
|
|
144
|
+
--user-agent 'NeoAgent catbox-upload-skill/1.0' \
|
|
145
|
+
-F 'reqtype=fileupload' \
|
|
146
|
+
-F "fileToUpload=@$CURL_FILE_PATH" \
|
|
147
|
+
"$ENDPOINT"
|
|
148
|
+
)"
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
RESPONSE="$(printf '%s' "$RESPONSE" | tr -d '\r\n')"
|
|
153
|
+
[[ "$RESPONSE" =~ ^https?:// ]] || fail "upload failed: $RESPONSE"
|
|
154
|
+
|
|
155
|
+
printf '%s\n' "$RESPONSE"
|
|
@@ -10,6 +10,7 @@ const { logRequestSummary } = require('../utils/logger');
|
|
|
10
10
|
const { getSessionSecret } = require('../services/account/session_secret');
|
|
11
11
|
|
|
12
12
|
const sessionsDb = new Sqlite(`${DATA_DIR}/sessions.db`);
|
|
13
|
+
const LEGACY_SESSION_EXPIRE_FALLBACK = 0;
|
|
13
14
|
|
|
14
15
|
function ensureSessionStoreSchema(db) {
|
|
15
16
|
const table = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'sessions'").get();
|
|
@@ -35,11 +36,10 @@ function ensureSessionStoreSchema(db) {
|
|
|
35
36
|
db.exec('CREATE TABLE sessions (sid PRIMARY KEY, sess, expire)');
|
|
36
37
|
|
|
37
38
|
if (hasSid && hasSess) {
|
|
38
|
-
// TODO: Verify whether better-sqlite3-session-store treats expire=0 as already expired or non-expiring.
|
|
39
39
|
const expireExpr = expireColumn ? expireColumn : 'NULL';
|
|
40
40
|
db.exec(`
|
|
41
41
|
INSERT OR REPLACE INTO sessions (sid, sess, expire)
|
|
42
|
-
SELECT sid, sess, COALESCE(${expireExpr},
|
|
42
|
+
SELECT sid, sess, COALESCE(${expireExpr}, ${LEGACY_SESSION_EXPIRE_FALLBACK}) AS expire
|
|
43
43
|
FROM ${legacyTableName}
|
|
44
44
|
WHERE sid IS NOT NULL
|
|
45
45
|
`);
|
|
@@ -138,6 +138,13 @@ function applyHttpMiddleware(app, { secureCookies, sessionMiddleware, validateOr
|
|
|
138
138
|
|| path === '/api/browser-extension/pairing/request'
|
|
139
139
|
|| /^\/api\/browser-extension\/pairing\/[^/]+\/claim$/i.test(path);
|
|
140
140
|
};
|
|
141
|
+
const requestPath = (req) => req.originalUrl || req.url || req.path || '';
|
|
142
|
+
const applyOnlyToRecordingChunk = (handler) => (req, res, next) => (
|
|
143
|
+
isRecordingChunkPath(requestPath(req)) ? handler(req, res, next) : next()
|
|
144
|
+
);
|
|
145
|
+
const skipRecordingChunk = (handler) => (req, res, next) => (
|
|
146
|
+
isRecordingChunkPath(requestPath(req)) ? next() : handler(req, res, next)
|
|
147
|
+
);
|
|
141
148
|
|
|
142
149
|
if (secureCookies) {
|
|
143
150
|
app.set('trust proxy', 1);
|
|
@@ -184,24 +191,9 @@ function applyHttpMiddleware(app, { secureCookies, sessionMiddleware, validateOr
|
|
|
184
191
|
|
|
185
192
|
next();
|
|
186
193
|
});
|
|
187
|
-
app.use((
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
return next();
|
|
192
|
-
});
|
|
193
|
-
app.use((req, res, next) => {
|
|
194
|
-
if (isRecordingChunkPath(req.originalUrl || req.url || req.path)) {
|
|
195
|
-
return next();
|
|
196
|
-
}
|
|
197
|
-
return jsonBody(req, res, next);
|
|
198
|
-
});
|
|
199
|
-
app.use((req, res, next) => {
|
|
200
|
-
if (isRecordingChunkPath(req.originalUrl || req.url || req.path)) {
|
|
201
|
-
return next();
|
|
202
|
-
}
|
|
203
|
-
return urlencodedBody(req, res, next);
|
|
204
|
-
});
|
|
194
|
+
app.use(applyOnlyToRecordingChunk(rawRecordingChunkBody));
|
|
195
|
+
app.use(skipRecordingChunk(jsonBody));
|
|
196
|
+
app.use(skipRecordingChunk(urlencodedBody));
|
|
205
197
|
app.use(sessionMiddleware);
|
|
206
198
|
}
|
|
207
199
|
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"425cfb54d01a9472b3e81d9e76fd63a4a44cfb
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "1235668503" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|