@heysummon/app 0.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/README.md +226 -0
- package/bin/CLAUDE.md +11 -0
- package/bin/cli.js +5 -0
- package/dist/__tests__/cli.test.js +81 -0
- package/dist/__tests__/config.test.js +99 -0
- package/dist/__tests__/secrets.test.js +60 -0
- package/dist/commands/init.js +155 -0
- package/dist/commands/start.js +138 -0
- package/dist/commands/status.js +91 -0
- package/dist/commands/stop.js +38 -0
- package/dist/commands/uninstall.js +139 -0
- package/dist/commands/update.js +33 -0
- package/dist/index.js +93 -0
- package/dist/lib/config.js +134 -0
- package/dist/lib/database.js +72 -0
- package/dist/lib/download.js +126 -0
- package/dist/lib/prompts.js +89 -0
- package/dist/lib/secrets.js +46 -0
- package/dist/lib/ui.js +176 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ฆ HeySummon CLI
|
|
4
|
+
|
|
5
|
+
**The fastest way to self-host HeySummon โ no Docker, no Git, one command.**
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/heysummon)
|
|
8
|
+
[](https://github.com/thomasansems/heysummon/blob/main/LICENSE.md)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
|
|
11
|
+
[Documentation](https://docs.heysummon.ai) ยท [Cloud](https://cloud.heysummon.ai) ยท [GitHub](https://github.com/thomasansems/heysummon)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What is HeySummon?
|
|
18
|
+
|
|
19
|
+
HeySummon is an open-source **Human-in-the-Loop platform for AI agents**. When an AI agent gets stuck, needs approval, or requires human context, it sends an encrypted help request to a human expert โ in real time, end-to-end encrypted, via a clean dashboard.
|
|
20
|
+
|
|
21
|
+
Think of it as **a pager for your AI agents**: they summon a human when they hit a wall, get a response, and continue their workflow โ all without breaking the loop.
|
|
22
|
+
|
|
23
|
+
- **E2E encrypted** โ RSA-OAEP + AES-256-GCM. The server never reads your messages.
|
|
24
|
+
- **Real-time** โ SSE-powered push updates, no polling needed.
|
|
25
|
+
- **Self-hostable** โ full control, runs on a single machine with SQLite.
|
|
26
|
+
- **Or use the cloud** โ [cloud.heysummon.ai](https://cloud.heysummon.ai) if you'd rather not host anything.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Why a CLI?
|
|
31
|
+
|
|
32
|
+
The HeySummon platform is a full Next.js application. The CLI exists so you can install and manage it without touching Git, Docker, or config files manually. It handles:
|
|
33
|
+
|
|
34
|
+
- Downloading the latest release from GitHub
|
|
35
|
+
- Generating cryptographic secrets
|
|
36
|
+
- Configuring your environment interactively
|
|
37
|
+
- Setting up the SQLite database and running migrations
|
|
38
|
+
- Building the app
|
|
39
|
+
- Starting, stopping, and updating the server
|
|
40
|
+
|
|
41
|
+
One command gets you from zero to a running server.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx heysummon
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The interactive installer walks you through everything. Takes ~2 minutes. No Docker or Git required.
|
|
52
|
+
|
|
53
|
+
Once installed, open the URL shown in the terminal to create your account.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
heysummon [command]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Command | Description |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `heysummon` / `heysummon init` | First-time setup โ download, configure, build, and start |
|
|
66
|
+
| `heysummon start` | Start the server |
|
|
67
|
+
| `heysummon start -d` | Start the server in the background (daemon) |
|
|
68
|
+
| `heysummon stop` | Stop the server |
|
|
69
|
+
| `heysummon status` | Check if the server is running |
|
|
70
|
+
| `heysummon update` | Update to the latest release |
|
|
71
|
+
| `heysummon uninstall` | Safely remove all data and stop the server |
|
|
72
|
+
|
|
73
|
+
### Options
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
--help, -h Show help
|
|
77
|
+
--version, -v Show version
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## What gets installed
|
|
83
|
+
|
|
84
|
+
Everything lives in `~/.heysummon/`:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
~/.heysummon/
|
|
88
|
+
โโโ app/ โ Next.js server (downloaded from GitHub)
|
|
89
|
+
โ โโโ prisma/
|
|
90
|
+
โ โ โโโ heysummon.db โ SQLite database (your data)
|
|
91
|
+
โ โโโ .next/ โ build output
|
|
92
|
+
โโโ .env โ config & secrets
|
|
93
|
+
โโโ heysummon.pid โ running server PID
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The CLI binary itself is installed separately via npm and is not part of `~/.heysummon/`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Updating
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
heysummon update
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Downloads the latest release, runs migrations, rebuilds, and restarts โ your data is preserved.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Uninstalling
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
heysummon uninstall
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Stops the server, offers a database backup, asks for explicit confirmation, and removes `~/.heysummon/`. Then remove the CLI binary:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm uninstall -g heysummon
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
> **Note:** `npm uninstall` alone only removes the CLI binary โ it does **not** touch `~/.heysummon/`. Always run `heysummon uninstall` first for a clean removal.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
| | Minimum |
|
|
129
|
+
|---|---|
|
|
130
|
+
| Node.js | 18+ |
|
|
131
|
+
| OS | Linux, macOS, WSL2 |
|
|
132
|
+
| Disk | ~500 MB (app + build) |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Alternative installation methods
|
|
137
|
+
|
|
138
|
+
The CLI is the easiest path but not the only one. Choose based on your use case:
|
|
139
|
+
|
|
140
|
+
### Docker (recommended for production)
|
|
141
|
+
|
|
142
|
+
Full stack with PostgreSQL, Guard reverse proxy, and optional tunnel:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git clone https://github.com/thomasansems/heysummon.git
|
|
146
|
+
cd heysummon
|
|
147
|
+
cp .env.example .env # edit secrets
|
|
148
|
+
docker compose up -d
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Make it publicly accessible
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Cloudflare Tunnel (recommended for production)
|
|
155
|
+
docker compose --profile cloudflare up -d
|
|
156
|
+
|
|
157
|
+
# Tailscale Funnel (great for teams, zero config firewall)
|
|
158
|
+
docker compose --profile tailscale up -d
|
|
159
|
+
|
|
160
|
+
# Ngrok (quick testing)
|
|
161
|
+
docker compose --profile ngrok up -d
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See the [Self-Hosting Guide](https://docs.heysummon.ai/self-hosting) for per-provider setup.
|
|
165
|
+
|
|
166
|
+
### Managed Cloud
|
|
167
|
+
|
|
168
|
+
No self-hosting at all โ [cloud.heysummon.ai](https://cloud.heysummon.ai) is the hosted version with a free tier.
|
|
169
|
+
|
|
170
|
+
### Comparison
|
|
171
|
+
|
|
172
|
+
| Method | Database | Tunnel | Best for |
|
|
173
|
+
|---|---|---|---|
|
|
174
|
+
| `npx heysummon` (this CLI) | SQLite | Manual / reverse proxy | Quick start, single machine |
|
|
175
|
+
| Docker Compose | PostgreSQL | Cloudflare / Tailscale / Ngrok | Production, teams |
|
|
176
|
+
| Cloud | Managed | Built-in | Zero ops |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Using HeySummon from an AI agent
|
|
181
|
+
|
|
182
|
+
Once your server is running, install the SDK in your agent's project:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm install @heysummon/sdk
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { HeySummon } from "@heysummon/sdk";
|
|
190
|
+
|
|
191
|
+
const hs = new HeySummon({ apiKey: "hs_cli_..." });
|
|
192
|
+
|
|
193
|
+
const response = await hs.ask("Should I proceed with deleting the old records?");
|
|
194
|
+
console.log(response); // human's answer, E2E decrypted
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Get your API key from the dashboard under **Settings โ API Keys**.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Documentation
|
|
202
|
+
|
|
203
|
+
- **[Getting started](https://docs.heysummon.ai/getting-started/quickstart)**
|
|
204
|
+
- **[SDK reference](https://docs.heysummon.ai/sdk)**
|
|
205
|
+
- **[Self-hosting guide](https://docs.heysummon.ai/self-hosting)**
|
|
206
|
+
- **[API reference](https://docs.heysummon.ai/api)**
|
|
207
|
+
- **[GitHub repository](https://github.com/thomasansems/heysummon)**
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
HeySummon uses a sustainable use model:
|
|
214
|
+
|
|
215
|
+
- **Core platform** โ [Sustainable Use License](https://github.com/thomasansems/heysummon/blob/main/LICENSE.md). Free for personal and internal business use.
|
|
216
|
+
- **Cloud features** โ separate [HeySummon Cloud License](https://github.com/thomasansems/heysummon/blob/main/LICENSE_CLOUD.md).
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
<div align="center">
|
|
221
|
+
|
|
222
|
+
**[docs.heysummon.ai](https://docs.heysummon.ai)** ยท **[cloud.heysummon.ai](https://cloud.heysummon.ai)** ยท **[GitHub](https://github.com/thomasansems/heysummon)**
|
|
223
|
+
|
|
224
|
+
Made with ๐ฆ by the HeySummon team
|
|
225
|
+
|
|
226
|
+
</div>
|
package/bin/CLAUDE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<claude-mem-context>
|
|
2
|
+
# Recent Activity
|
|
3
|
+
|
|
4
|
+
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
|
5
|
+
|
|
6
|
+
### Mar 23, 2026
|
|
7
|
+
|
|
8
|
+
| ID | Time | T | Title | Read |
|
|
9
|
+
|----|------|---|-------|------|
|
|
10
|
+
| #3341 | 2:18 PM | ๐ต | HeySummon CLI Wizard Flow Lacks Platform Selection | ~699 |
|
|
11
|
+
</claude-mem-context>
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const CLI_PATH = path.join(__dirname, "..", "..", "bin", "cli.js");
|
|
41
|
+
(0, node_test_1.describe)("CLI", () => {
|
|
42
|
+
(0, node_test_1.it)("prints version with --version flag", () => {
|
|
43
|
+
const output = (0, child_process_1.execSync)(`node "${CLI_PATH}" --version`, {
|
|
44
|
+
encoding: "utf-8",
|
|
45
|
+
}).trim();
|
|
46
|
+
assert.match(output, /^\d+\.\d+\.\d+$/);
|
|
47
|
+
});
|
|
48
|
+
(0, node_test_1.it)("prints version with -v flag", () => {
|
|
49
|
+
const output = (0, child_process_1.execSync)(`node "${CLI_PATH}" -v`, {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
}).trim();
|
|
52
|
+
assert.match(output, /^\d+\.\d+\.\d+$/);
|
|
53
|
+
});
|
|
54
|
+
(0, node_test_1.it)("prints help with --help flag", () => {
|
|
55
|
+
const output = (0, child_process_1.execSync)(`node "${CLI_PATH}" --help`, {
|
|
56
|
+
encoding: "utf-8",
|
|
57
|
+
});
|
|
58
|
+
assert.ok(output.includes("HeySummon CLI"));
|
|
59
|
+
assert.ok(output.includes("Usage:"));
|
|
60
|
+
assert.ok(output.includes("Commands:"));
|
|
61
|
+
assert.ok(output.includes("init"));
|
|
62
|
+
assert.ok(output.includes("start"));
|
|
63
|
+
assert.ok(output.includes("stop"));
|
|
64
|
+
assert.ok(output.includes("status"));
|
|
65
|
+
assert.ok(output.includes("update"));
|
|
66
|
+
});
|
|
67
|
+
(0, node_test_1.it)("prints help with -h flag", () => {
|
|
68
|
+
const output = (0, child_process_1.execSync)(`node "${CLI_PATH}" -h`, {
|
|
69
|
+
encoding: "utf-8",
|
|
70
|
+
});
|
|
71
|
+
assert.ok(output.includes("HeySummon CLI"));
|
|
72
|
+
});
|
|
73
|
+
(0, node_test_1.it)("exits with error for unknown commands", () => {
|
|
74
|
+
assert.throws(() => {
|
|
75
|
+
(0, child_process_1.execSync)(`node "${CLI_PATH}" foobar`, {
|
|
76
|
+
encoding: "utf-8",
|
|
77
|
+
stdio: "pipe",
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const config_1 = require("../lib/config");
|
|
39
|
+
(0, node_test_1.describe)("config", () => {
|
|
40
|
+
(0, node_test_1.describe)("generateEnv", () => {
|
|
41
|
+
const baseConfig = {
|
|
42
|
+
port: 3435,
|
|
43
|
+
publicUrl: "http://localhost:3435",
|
|
44
|
+
enableFormLogin: true,
|
|
45
|
+
enableGithubOauth: false,
|
|
46
|
+
enableGoogleOauth: false,
|
|
47
|
+
};
|
|
48
|
+
const secrets = {
|
|
49
|
+
nextauthSecret: "abc123",
|
|
50
|
+
};
|
|
51
|
+
(0, node_test_1.it)("generates env with all required variables", () => {
|
|
52
|
+
const env = (0, config_1.generateEnv)(baseConfig, secrets);
|
|
53
|
+
assert.ok(env.includes("DATABASE_URL="));
|
|
54
|
+
assert.ok(env.includes("PORT=3435"));
|
|
55
|
+
assert.ok(env.includes('NEXTAUTH_URL="http://localhost:3435"'));
|
|
56
|
+
assert.ok(env.includes('NEXTAUTH_SECRET="abc123"'));
|
|
57
|
+
assert.ok(env.includes('ENABLE_FORM_LOGIN="true"'));
|
|
58
|
+
});
|
|
59
|
+
(0, node_test_1.it)("includes GitHub OAuth when enabled", () => {
|
|
60
|
+
const config = {
|
|
61
|
+
...baseConfig,
|
|
62
|
+
enableGithubOauth: true,
|
|
63
|
+
githubId: "gh-id",
|
|
64
|
+
githubSecret: "gh-secret",
|
|
65
|
+
};
|
|
66
|
+
const env = (0, config_1.generateEnv)(config, secrets);
|
|
67
|
+
assert.ok(env.includes('GITHUB_ID="gh-id"'));
|
|
68
|
+
assert.ok(env.includes('GITHUB_SECRET="gh-secret"'));
|
|
69
|
+
});
|
|
70
|
+
(0, node_test_1.it)("includes Google OAuth when enabled", () => {
|
|
71
|
+
const config = {
|
|
72
|
+
...baseConfig,
|
|
73
|
+
enableGoogleOauth: true,
|
|
74
|
+
googleId: "g-id",
|
|
75
|
+
googleSecret: "g-secret",
|
|
76
|
+
};
|
|
77
|
+
const env = (0, config_1.generateEnv)(config, secrets);
|
|
78
|
+
assert.ok(env.includes('GOOGLE_ID="g-id"'));
|
|
79
|
+
assert.ok(env.includes('GOOGLE_SECRET="g-secret"'));
|
|
80
|
+
});
|
|
81
|
+
(0, node_test_1.it)("excludes OAuth vars when disabled", () => {
|
|
82
|
+
const env = (0, config_1.generateEnv)(baseConfig, secrets);
|
|
83
|
+
assert.ok(!env.includes("GITHUB_ID"));
|
|
84
|
+
assert.ok(!env.includes("GITHUB_SECRET"));
|
|
85
|
+
assert.ok(!env.includes("GOOGLE_ID"));
|
|
86
|
+
assert.ok(!env.includes("GOOGLE_SECRET"));
|
|
87
|
+
});
|
|
88
|
+
(0, node_test_1.it)("uses custom port in output", () => {
|
|
89
|
+
const config = {
|
|
90
|
+
...baseConfig,
|
|
91
|
+
port: 8080,
|
|
92
|
+
publicUrl: "https://my.domain.com",
|
|
93
|
+
};
|
|
94
|
+
const env = (0, config_1.generateEnv)(config, secrets);
|
|
95
|
+
assert.ok(env.includes("PORT=8080"));
|
|
96
|
+
assert.ok(env.includes('NEXTAUTH_URL="https://my.domain.com"'));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const secrets_1 = require("../lib/secrets");
|
|
39
|
+
(0, node_test_1.describe)("secrets", () => {
|
|
40
|
+
(0, node_test_1.it)("generates a hex string of correct length", () => {
|
|
41
|
+
const secret = (0, secrets_1.generateSecret)(32);
|
|
42
|
+
assert.strictEqual(secret.length, 64); // 32 bytes = 64 hex chars
|
|
43
|
+
assert.match(secret, /^[0-9a-f]{64}$/);
|
|
44
|
+
});
|
|
45
|
+
(0, node_test_1.it)("generates unique secrets each time", () => {
|
|
46
|
+
const a = (0, secrets_1.generateSecret)();
|
|
47
|
+
const b = (0, secrets_1.generateSecret)();
|
|
48
|
+
assert.notStrictEqual(a, b);
|
|
49
|
+
});
|
|
50
|
+
(0, node_test_1.it)("generates the required secret", () => {
|
|
51
|
+
const secrets = (0, secrets_1.generateSecrets)();
|
|
52
|
+
assert.ok(secrets.nextauthSecret);
|
|
53
|
+
assert.match(secrets.nextauthSecret, /^[0-9a-f]{64}$/);
|
|
54
|
+
});
|
|
55
|
+
(0, node_test_1.it)("respects custom byte length", () => {
|
|
56
|
+
const short = (0, secrets_1.generateSecret)(16);
|
|
57
|
+
assert.strictEqual(short.length, 32); // 16 bytes = 32 hex chars
|
|
58
|
+
assert.match(short, /^[0-9a-f]{32}$/);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.init = init;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const p = __importStar(require("@clack/prompts"));
|
|
39
|
+
const config_1 = require("../lib/config");
|
|
40
|
+
const secrets_1 = require("../lib/secrets");
|
|
41
|
+
const download_1 = require("../lib/download");
|
|
42
|
+
const database_1 = require("../lib/database");
|
|
43
|
+
const prompts_1 = require("../lib/prompts");
|
|
44
|
+
const start_1 = require("./start");
|
|
45
|
+
const ui_1 = require("../lib/ui");
|
|
46
|
+
async function init(opts) {
|
|
47
|
+
const quickstart = opts?.yes ?? false;
|
|
48
|
+
await (0, ui_1.printAnimatedBanner)();
|
|
49
|
+
p.intro("heysummon init");
|
|
50
|
+
const heysummonDir = (0, config_1.getHeysummonDir)();
|
|
51
|
+
p.log.info(`Home: ${ui_1.color.cyan(heysummonDir)}`);
|
|
52
|
+
// Check existing installation
|
|
53
|
+
if ((0, config_1.isInitialized)()) {
|
|
54
|
+
if (quickstart) {
|
|
55
|
+
p.log.warn("Existing installation found โ reinstalling.");
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const reinit = await (0, prompts_1.askYesNo)("HeySummon is already installed. Reinstall from scratch?", false);
|
|
59
|
+
if (!reinit) {
|
|
60
|
+
p.outro(`Tip: use ${ui_1.color.cyan("heysummon start")} to run the server.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const appDir = (0, config_1.getAppDir)();
|
|
65
|
+
if (fs.existsSync(appDir)) {
|
|
66
|
+
fs.rmSync(appDir, { recursive: true });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Create directory structure
|
|
70
|
+
(0, config_1.ensureDir)((0, config_1.getHeysummonDir)());
|
|
71
|
+
(0, config_1.ensureDir)((0, config_1.getAppDir)());
|
|
72
|
+
// โโ Download โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
73
|
+
const dlSpinner = p.spinner();
|
|
74
|
+
dlSpinner.start("Downloading latest release from GitHub...");
|
|
75
|
+
const version = await (0, download_1.downloadAndExtract)();
|
|
76
|
+
dlSpinner.stop(`Downloaded HeySummon ${version}`);
|
|
77
|
+
// โโ Configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
78
|
+
let port = 3435;
|
|
79
|
+
let publicUrl = `http://localhost:${port}`;
|
|
80
|
+
if (!quickstart) {
|
|
81
|
+
const portInput = await p.text({
|
|
82
|
+
message: "Guard port (app entry point)",
|
|
83
|
+
defaultValue: "3435",
|
|
84
|
+
placeholder: "3435",
|
|
85
|
+
});
|
|
86
|
+
if (p.isCancel(portInput)) {
|
|
87
|
+
p.cancel("Setup cancelled.");
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
port = parseInt(String(portInput), 10) || 3435;
|
|
91
|
+
p.log.info(`HeySummon will be available at: ${ui_1.color.cyan(`http://localhost:${port}`)}`);
|
|
92
|
+
const urlInput = await p.text({
|
|
93
|
+
message: "Public URL (for internet access, or keep default)",
|
|
94
|
+
defaultValue: `http://localhost:${port}`,
|
|
95
|
+
placeholder: `http://localhost:${port}`,
|
|
96
|
+
});
|
|
97
|
+
if (p.isCancel(urlInput)) {
|
|
98
|
+
p.cancel("Setup cancelled.");
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
publicUrl = String(urlInput);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
p.log.info(`Using defaults: port ${ui_1.color.cyan("3435")}, URL ${ui_1.color.cyan(publicUrl)}`);
|
|
105
|
+
}
|
|
106
|
+
// โโ Secrets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
107
|
+
const secretsSpinner = p.spinner();
|
|
108
|
+
secretsSpinner.start("Generating secure random secrets...");
|
|
109
|
+
const secrets = (0, secrets_1.generateSecrets)();
|
|
110
|
+
const config = {
|
|
111
|
+
port,
|
|
112
|
+
publicUrl,
|
|
113
|
+
enableFormLogin: true,
|
|
114
|
+
enableGithubOauth: false,
|
|
115
|
+
enableGoogleOauth: false,
|
|
116
|
+
};
|
|
117
|
+
const envContent = (0, config_1.generateEnv)(config, secrets);
|
|
118
|
+
(0, config_1.writeEnv)(envContent);
|
|
119
|
+
secretsSpinner.stop("Secrets generated and saved");
|
|
120
|
+
// โโ Database โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
121
|
+
const dbSpinner = p.spinner();
|
|
122
|
+
dbSpinner.start("Setting up SQLite database and running migrations...");
|
|
123
|
+
(0, database_1.installDependencies)();
|
|
124
|
+
(0, database_1.runMigrations)();
|
|
125
|
+
dbSpinner.stop("Database ready");
|
|
126
|
+
// โโ Build โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
127
|
+
const buildSpinner = p.spinner();
|
|
128
|
+
buildSpinner.start("Building application (this takes ~30 seconds)...");
|
|
129
|
+
(0, database_1.buildApp)();
|
|
130
|
+
buildSpinner.stop("Build complete");
|
|
131
|
+
// โโ Configuration summary โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
132
|
+
p.note([
|
|
133
|
+
`Port: ${port}`,
|
|
134
|
+
`URL: ${publicUrl}`,
|
|
135
|
+
`Database: SQLite`,
|
|
136
|
+
`Auth: Form login`,
|
|
137
|
+
`Home: ${heysummonDir}`,
|
|
138
|
+
].join("\n"), "Configuration");
|
|
139
|
+
// โโ Start daemon โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
140
|
+
const startSpinner = p.spinner();
|
|
141
|
+
startSpinner.start("Starting HeySummon in the background...");
|
|
142
|
+
try {
|
|
143
|
+
await (0, start_1.startDaemon)(port);
|
|
144
|
+
startSpinner.stop("HeySummon is running!");
|
|
145
|
+
p.log.info(`Dashboard: ${ui_1.color.cyan(publicUrl)}`);
|
|
146
|
+
p.log.info(`Docs: ${ui_1.color.cyan("https://docs.heysummon.ai/getting-started/quickstart")}`);
|
|
147
|
+
p.log.info(`Status: ${ui_1.color.cyan("heysummon status")}`);
|
|
148
|
+
p.log.info(`Stop: ${ui_1.color.cyan("heysummon stop")}`);
|
|
149
|
+
p.outro(`Open ${ui_1.color.cyan(publicUrl)} to create your account.`);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
startSpinner.stop("Could not start automatically.");
|
|
153
|
+
p.outro(`Run ${ui_1.color.cyan("heysummon start -d")} to start manually.`);
|
|
154
|
+
}
|
|
155
|
+
}
|