create-terse 0.0.1
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.md +83 -0
- package/README.md +55 -0
- package/dist/index.js +305 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
- package/templates/README.md +103 -0
- package/templates/docker-compose.yml +59 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
Portions of this software are licensed as follows:
|
|
4
|
+
|
|
5
|
+
- Content of branches other than the main branch (i.e. "main") are not licensed.
|
|
6
|
+
- All third party components incorporated into the Terse Software are licensed under the original license
|
|
7
|
+
provided by the owner of the applicable component.
|
|
8
|
+
- Content outside of the above mentioned files or restrictions is available under the "Sustainable Use
|
|
9
|
+
License" as defined below.
|
|
10
|
+
|
|
11
|
+
## Sustainable Use License
|
|
12
|
+
|
|
13
|
+
Version 1.0
|
|
14
|
+
|
|
15
|
+
### Acceptance
|
|
16
|
+
|
|
17
|
+
By using the software, you agree to all of the terms and conditions below.
|
|
18
|
+
|
|
19
|
+
### Copyright License
|
|
20
|
+
|
|
21
|
+
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license
|
|
22
|
+
to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject
|
|
23
|
+
to the limitations below.
|
|
24
|
+
|
|
25
|
+
### Limitations
|
|
26
|
+
|
|
27
|
+
You may use or modify the software only for your own internal business purposes or for non-commercial or
|
|
28
|
+
personal use. You may distribute the software or provide it to others only if you do so free of charge for
|
|
29
|
+
non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of
|
|
30
|
+
the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law.
|
|
31
|
+
|
|
32
|
+
### Patents
|
|
33
|
+
|
|
34
|
+
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to
|
|
35
|
+
license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case
|
|
36
|
+
subject to the limitations and conditions in this license. This license does not cover any patent claims that
|
|
37
|
+
you cause to be infringed by modifications or additions to the software. If you or your company make any
|
|
38
|
+
written claim that the software infringes or contributes to infringement of any patent, your patent license
|
|
39
|
+
for the software granted under these terms ends immediately. If your company makes such a claim, your patent
|
|
40
|
+
license ends immediately for work on behalf of your company.
|
|
41
|
+
|
|
42
|
+
### Notices
|
|
43
|
+
|
|
44
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these
|
|
45
|
+
terms. If you modify the software, you must include in any modified copies of the software a prominent notice
|
|
46
|
+
stating that you have modified the software.
|
|
47
|
+
|
|
48
|
+
### No Other Rights
|
|
49
|
+
|
|
50
|
+
These terms do not imply any licenses other than those expressly granted in these terms.
|
|
51
|
+
|
|
52
|
+
### Termination
|
|
53
|
+
|
|
54
|
+
If you use the software in violation of these terms, such use is not licensed, and your license will
|
|
55
|
+
automatically terminate. If the licensor provides you with a notice of your violation, and you cease all
|
|
56
|
+
violation of this license no later than 30 days after you receive that notice, your license will be reinstated
|
|
57
|
+
retroactively. However, if you violate these terms after such reinstatement, any additional violation of these
|
|
58
|
+
terms will cause your license to terminate automatically and permanently.
|
|
59
|
+
|
|
60
|
+
### No Liability
|
|
61
|
+
|
|
62
|
+
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will
|
|
63
|
+
not be liable to you for any damages arising out of these terms or the use or nature of the software, under
|
|
64
|
+
any kind of legal claim.
|
|
65
|
+
|
|
66
|
+
### Definitions
|
|
67
|
+
|
|
68
|
+
The “licensor” is the entity offering these terms.
|
|
69
|
+
|
|
70
|
+
The “software” is the software the licensor makes available under these terms, including any portion of it.
|
|
71
|
+
|
|
72
|
+
“You” refers to the individual or entity agreeing to these terms.
|
|
73
|
+
|
|
74
|
+
“Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus
|
|
75
|
+
all organizations that have control over, are under the control of, or are under common control with that
|
|
76
|
+
organization. Control means ownership of substantially all the assets of an entity, or the power to direct its
|
|
77
|
+
management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
78
|
+
|
|
79
|
+
“Your license” is the license granted to you for the software under these terms.
|
|
80
|
+
|
|
81
|
+
“Use” means anything you do with the software requiring your license.
|
|
82
|
+
|
|
83
|
+
“Trademark” means trademarks, service marks, and similar rights.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# create-terse
|
|
2
|
+
|
|
3
|
+
Scaffold and launch a self-hosted [Terse](https://useterse.ai) instance with one command.
|
|
4
|
+
|
|
5
|
+
Terse is a code-first AI workflow platform. You write workflows in TypeScript, generate a typed SDK from your connected integrations, and deploy them. Full docs at [docs.useterse.ai](https://docs.useterse.ai).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm create terse
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
or
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx create-terse
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This walks you through a short setup, writes a `docker-compose.yml` and `.env` into a target directory, pulls the Terse image, starts the services (Postgres, backend, frontend), and installs the `terse` CLI pointed at your new instance.
|
|
20
|
+
|
|
21
|
+
## Options
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx create-terse [options]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Option | Description |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `-y`, `--non-interactive` | Skip prompts and use defaults (auto-enabled when stdin/stdout is not a TTY). |
|
|
30
|
+
| `--target <dir>` | Target directory (default: `./terse`). |
|
|
31
|
+
| `--frontend-url <url>` | Frontend URL as seen from your browser (default: `http://localhost:5173`). |
|
|
32
|
+
| `--backend-url <url>` | Backend URL as seen from your browser (default: `http://localhost:3001`). |
|
|
33
|
+
| `--no-open` | Don't open the frontend in your browser when setup completes. |
|
|
34
|
+
| `-h`, `--help` | Show help. |
|
|
35
|
+
|
|
36
|
+
### Examples
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm create terse
|
|
40
|
+
npx create-terse -y
|
|
41
|
+
npx create-terse -y --target ./my-terse --backend-url http://localhost:3001
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Requirements
|
|
45
|
+
|
|
46
|
+
- Node.js >= 18
|
|
47
|
+
- Docker with Compose v2 (`docker compose`)
|
|
48
|
+
|
|
49
|
+
## After setup
|
|
50
|
+
|
|
51
|
+
The generated `README.md` in your target directory covers daily operations (logs, upgrades, backups, troubleshooting). See the [self-hosting guide](https://docs.useterse.ai/self-hosting) for the full walkthrough.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
See [LICENSE.md](./LICENSE.md).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cancel, intro, isCancel, log, outro, spinner, text } from "@clack/prompts";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { execSync, spawn } from "node:child_process";
|
|
5
|
+
import { exec } from "node:child_process";
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import net from "node:net";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const TEMPLATES_DIR = path.resolve(SCRIPT_DIR, "../templates");
|
|
15
|
+
const DEFAULTS = {
|
|
16
|
+
targetDir: "./terse",
|
|
17
|
+
frontendUrl: "http://localhost:5173",
|
|
18
|
+
backendUrl: "http://localhost:3001"
|
|
19
|
+
};
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = parseArgs(process.argv.slice(2));
|
|
22
|
+
if (args.help) {
|
|
23
|
+
printHelp();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const nonInteractive = args.nonInteractive || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
27
|
+
intro(chalk.bold(chalk.magenta(" Terse self-host setup ")));
|
|
28
|
+
if (nonInteractive)
|
|
29
|
+
log.info("Running non-interactively. Pass --target / --frontend-url / --backend-url to override defaults.");
|
|
30
|
+
const targetDir = await promptOr(nonInteractive, args.targetDir, "Where should we set up Terse?", DEFAULTS.targetDir);
|
|
31
|
+
const resolved = path.resolve(targetDir);
|
|
32
|
+
await fs.mkdir(resolved, { recursive: true });
|
|
33
|
+
await fs.copyFile(path.join(TEMPLATES_DIR, "docker-compose.yml"), path.join(resolved, "docker-compose.yml"));
|
|
34
|
+
await fs.copyFile(path.join(TEMPLATES_DIR, "README.md"), path.join(resolved, "README.md"));
|
|
35
|
+
const frontendUrl = await promptOr(nonInteractive, args.frontendUrl, "Frontend URL (as seen from your browser)", DEFAULTS.frontendUrl);
|
|
36
|
+
const backendUrl = await promptOr(nonInteractive, args.backendUrl, "Backend URL (as seen from your browser)", DEFAULTS.backendUrl);
|
|
37
|
+
const postgresPort = await findFreePort(5432);
|
|
38
|
+
if (postgresPort !== 5432) {
|
|
39
|
+
log.warn(`Port 5432 is taken on this machine, exposing the Terse Postgres container on ${postgresPort} instead.`);
|
|
40
|
+
}
|
|
41
|
+
const frontendPort = hostPortFromUrl(frontendUrl);
|
|
42
|
+
const backendPort = hostPortFromUrl(backendUrl);
|
|
43
|
+
await writeEnv(resolved, { frontendUrl, backendUrl, frontendPort, backendPort, postgresPort });
|
|
44
|
+
log.success(`Wrote .env (edit ${path.join(targetDir, ".env")} to tweak ports, URLs, or secrets)`);
|
|
45
|
+
await runStepStreaming("Pulling Terse image (first run downloads ~500MB)", "docker compose pull", resolved);
|
|
46
|
+
await runStep("Starting Terse services", "docker compose up -d", resolved);
|
|
47
|
+
await waitForBackend(backendUrl);
|
|
48
|
+
await installTerseCli();
|
|
49
|
+
await pointTerseCliAtLocal({ frontendUrl, backendUrl });
|
|
50
|
+
printNextSteps({ frontendUrl });
|
|
51
|
+
outro(chalk.green(`Setup complete. Terse is running at ${chalk.bold(frontendUrl)}.`));
|
|
52
|
+
if (!nonInteractive && !args.noOpen) {
|
|
53
|
+
await openInBrowser(frontendUrl);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function openInBrowser(url) {
|
|
57
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
58
|
+
try {
|
|
59
|
+
await execAsync(`${opener} ${shellEscape(url)}`);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// browser open is a nice-to-have; if it fails the user already has the URL in the outro
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function printNextSteps(args) {
|
|
66
|
+
log.message([
|
|
67
|
+
chalk.bold("Next steps:"),
|
|
68
|
+
` 1. Open ${chalk.cyan(args.frontendUrl)} ${chalk.dim("to bootstrap your single admin user.")}`,
|
|
69
|
+
` 2. ${chalk.cyan("terse init my-job")} ${chalk.dim("# scaffold your first job (CLI is installed and pointed at your local backend).")}`,
|
|
70
|
+
"",
|
|
71
|
+
chalk.dim("See `README.md` in this folder for daily ops (logs, upgrades, backups, troubleshooting)."),
|
|
72
|
+
chalk.dim("Run `terse target` any time to see which backend the CLI is pointing at.")
|
|
73
|
+
].join("\n"));
|
|
74
|
+
}
|
|
75
|
+
async function installTerseCli() {
|
|
76
|
+
if (commandExists("terse")) {
|
|
77
|
+
log.info("terse CLI already installed, skipping global install");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await runStep("Installing terse CLI globally (npm i -g terse-cli)", "npm install -g terse-cli", process.cwd());
|
|
81
|
+
}
|
|
82
|
+
async function pointTerseCliAtLocal(urls) {
|
|
83
|
+
if (!commandExists("terse")) {
|
|
84
|
+
log.warn("terse CLI not on PATH, skipping `terse target use`. Run it manually once installed.");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const cmd = `terse target use --backend-url ${shellEscape(urls.backendUrl)} --frontend-url ${shellEscape(urls.frontendUrl)} --yes`;
|
|
88
|
+
await runStep(`Pointing terse CLI at ${urls.backendUrl}`, cmd, process.cwd());
|
|
89
|
+
}
|
|
90
|
+
function commandExists(name) {
|
|
91
|
+
try {
|
|
92
|
+
execSync(`command -v ${name}`, { stdio: "pipe" });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function writeEnv(targetDir, config) {
|
|
100
|
+
const socketUrl = toWebSocketUrl(config.backendUrl);
|
|
101
|
+
const lines = [
|
|
102
|
+
"# Terse self-host config. Edit values then `docker compose up -d` to apply.",
|
|
103
|
+
"",
|
|
104
|
+
"# ── URLs (what your browser sees) ─────────────────────────────",
|
|
105
|
+
`FRONTEND_URL=${config.frontendUrl}`,
|
|
106
|
+
`BACKEND_URL=${config.backendUrl}`,
|
|
107
|
+
"",
|
|
108
|
+
"# ── Host ports (must match the ports in the URLs above) ───────",
|
|
109
|
+
`FRONTEND_PORT=${config.frontendPort}`,
|
|
110
|
+
`BACKEND_PORT=${config.backendPort}`,
|
|
111
|
+
"",
|
|
112
|
+
"# ── Postgres ──────────────────────────────────────────────────",
|
|
113
|
+
"POSTGRES_USER=postgres",
|
|
114
|
+
"POSTGRES_PASSWORD=postgres",
|
|
115
|
+
"POSTGRES_DB=terse",
|
|
116
|
+
`POSTGRES_PORT=${config.postgresPort}`,
|
|
117
|
+
"",
|
|
118
|
+
"# ── Secrets (rotate by replacing the values) ──────────────────",
|
|
119
|
+
`JWT_SECRET=${randomBytes(32).toString("base64url")}`,
|
|
120
|
+
`LOCAL_SECRETS_ENCRYPTION_KEY=${randomBytes(32).toString("base64url")}`,
|
|
121
|
+
"",
|
|
122
|
+
"# ── Frontend (must match BACKEND_URL above) ───────────────────",
|
|
123
|
+
`VITE_API_BASE_URL=${config.backendUrl}`,
|
|
124
|
+
`VITE_BACKEND_REDIRECT_URL=${config.backendUrl}`,
|
|
125
|
+
`VITE_SOCKET_URL=${socketUrl}`,
|
|
126
|
+
"",
|
|
127
|
+
"# ── Internal backend URL (how the backend container reaches itself) ─",
|
|
128
|
+
"# Job runs talk to the backend from inside the container, where it",
|
|
129
|
+
"# always listens on 3001. This is NOT the host port in BACKEND_URL.",
|
|
130
|
+
"# Leave as-is unless you change the backend's in-container listen port.",
|
|
131
|
+
"INTERNAL_BACKEND_URL=http://localhost:3001",
|
|
132
|
+
"",
|
|
133
|
+
"# ── Runtime ───────────────────────────────────────────────────",
|
|
134
|
+
"NODE_ENV=development",
|
|
135
|
+
"",
|
|
136
|
+
"# ── Image (uncomment to pin to a specific tag) ────────────────",
|
|
137
|
+
"# TERSE_IMAGE=us-central1-docker.pkg.dev/fluid-analogy-473415-c2/public/terse",
|
|
138
|
+
""
|
|
139
|
+
];
|
|
140
|
+
await fs.writeFile(path.join(targetDir, ".env"), lines.join("\n"));
|
|
141
|
+
}
|
|
142
|
+
function toWebSocketUrl(httpUrl) {
|
|
143
|
+
const url = new URL(httpUrl);
|
|
144
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
145
|
+
return url.toString().replace(/\/$/, "");
|
|
146
|
+
}
|
|
147
|
+
function hostPortFromUrl(httpUrl) {
|
|
148
|
+
const url = new URL(httpUrl);
|
|
149
|
+
if (url.port)
|
|
150
|
+
return Number(url.port);
|
|
151
|
+
return url.protocol === "https:" ? 443 : 80;
|
|
152
|
+
}
|
|
153
|
+
async function promptOr(nonInteractive, override, message, placeholder) {
|
|
154
|
+
if (override !== undefined) {
|
|
155
|
+
log.info(`${message} ${chalk.dim(`→ ${override}`)}`);
|
|
156
|
+
return override;
|
|
157
|
+
}
|
|
158
|
+
if (nonInteractive) {
|
|
159
|
+
log.info(`${message} ${chalk.dim(`→ ${placeholder} (default)`)}`);
|
|
160
|
+
return placeholder;
|
|
161
|
+
}
|
|
162
|
+
const result = await text({ message, placeholder, defaultValue: placeholder });
|
|
163
|
+
if (isCancel(result)) {
|
|
164
|
+
cancel("Cancelled.");
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
function parseArgs(argv) {
|
|
170
|
+
const out = { nonInteractive: false, noOpen: false, help: false };
|
|
171
|
+
for (let i = 0; i < argv.length; i++) {
|
|
172
|
+
const arg = argv[i];
|
|
173
|
+
const next = () => {
|
|
174
|
+
const v = argv[++i];
|
|
175
|
+
if (v === undefined)
|
|
176
|
+
throw new Error(`Missing value for ${arg}`);
|
|
177
|
+
return v;
|
|
178
|
+
};
|
|
179
|
+
const valueOf = (flag) => {
|
|
180
|
+
const eq = arg.indexOf("=");
|
|
181
|
+
return eq >= 0 ? arg.slice(eq + 1) : next();
|
|
182
|
+
};
|
|
183
|
+
if (arg === "-y" || arg === "--yes" || arg === "--non-interactive")
|
|
184
|
+
out.nonInteractive = true;
|
|
185
|
+
else if (arg === "-h" || arg === "--help")
|
|
186
|
+
out.help = true;
|
|
187
|
+
else if (arg === "--target" || arg.startsWith("--target="))
|
|
188
|
+
out.targetDir = valueOf(arg);
|
|
189
|
+
else if (arg === "--frontend-url" || arg.startsWith("--frontend-url="))
|
|
190
|
+
out.frontendUrl = valueOf(arg);
|
|
191
|
+
else if (arg === "--backend-url" || arg.startsWith("--backend-url="))
|
|
192
|
+
out.backendUrl = valueOf(arg);
|
|
193
|
+
else if (arg === "--no-open")
|
|
194
|
+
out.noOpen = true;
|
|
195
|
+
else
|
|
196
|
+
throw new Error(`Unknown argument: ${arg}. Run with --help for usage.`);
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
function printHelp() {
|
|
201
|
+
const lines = [
|
|
202
|
+
chalk.bold("create-terse — scaffold a self-hosted Terse instance"),
|
|
203
|
+
"",
|
|
204
|
+
chalk.bold("Usage:"),
|
|
205
|
+
" npx create-terse [options]",
|
|
206
|
+
"",
|
|
207
|
+
chalk.bold("Options:"),
|
|
208
|
+
" -y, --non-interactive Skip prompts and use defaults (also auto-enabled when stdin/stdout is not a TTY)",
|
|
209
|
+
` --target <dir> Target directory (default: ${DEFAULTS.targetDir})`,
|
|
210
|
+
` --frontend-url <url> Frontend URL (default: ${DEFAULTS.frontendUrl})`,
|
|
211
|
+
` --backend-url <url> Backend URL (default: ${DEFAULTS.backendUrl})`,
|
|
212
|
+
" --no-open Skip opening the frontend in your browser when setup completes",
|
|
213
|
+
" -h, --help Show this help",
|
|
214
|
+
"",
|
|
215
|
+
chalk.bold("Examples:"),
|
|
216
|
+
" npx create-terse",
|
|
217
|
+
" npx create-terse -y",
|
|
218
|
+
" npx create-terse -y --target ./my-terse --backend-url http://localhost:3001"
|
|
219
|
+
];
|
|
220
|
+
console.log(lines.join("\n"));
|
|
221
|
+
}
|
|
222
|
+
async function runStep(label, command, cwd) {
|
|
223
|
+
const s = spinner();
|
|
224
|
+
s.start(label);
|
|
225
|
+
try {
|
|
226
|
+
await execAsync(command, { cwd });
|
|
227
|
+
s.stop(`${label} ✓`);
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
s.stop(`${label} ✗`);
|
|
231
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
232
|
+
const stdout = err.stdout ?? "";
|
|
233
|
+
const stderr = err.stderr ?? "";
|
|
234
|
+
throw new Error([message, stdout, stderr].filter(Boolean).join("\n"));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function runStepStreaming(label, command, cwd) {
|
|
238
|
+
log.info(label);
|
|
239
|
+
await new Promise((resolve, reject) => {
|
|
240
|
+
const child = spawn(command, { cwd, stdio: "inherit", shell: true });
|
|
241
|
+
child.on("exit", code => {
|
|
242
|
+
if (code === 0) {
|
|
243
|
+
log.success(`${label} ✓`);
|
|
244
|
+
resolve();
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
reject(new Error(`${label} failed with exit code ${code}`));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
child.on("error", reject);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function shellEscape(value) {
|
|
254
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
255
|
+
}
|
|
256
|
+
async function findFreePort(preferred) {
|
|
257
|
+
const candidates = [preferred, 54322, 54323, 54324, 54325];
|
|
258
|
+
for (const port of candidates) {
|
|
259
|
+
if (await isPortFree(port))
|
|
260
|
+
return port;
|
|
261
|
+
}
|
|
262
|
+
throw new Error("Could not find a free TCP port for Postgres");
|
|
263
|
+
}
|
|
264
|
+
async function isPortFree(port) {
|
|
265
|
+
const v4Busy = await tryConnect("127.0.0.1", port);
|
|
266
|
+
const v6Busy = await tryConnect("::1", port);
|
|
267
|
+
return !v4Busy && !v6Busy;
|
|
268
|
+
}
|
|
269
|
+
function tryConnect(host, port) {
|
|
270
|
+
return new Promise(resolve => {
|
|
271
|
+
const socket = net.connect({ host, port });
|
|
272
|
+
const done = (ok) => {
|
|
273
|
+
socket.removeAllListeners();
|
|
274
|
+
socket.destroy();
|
|
275
|
+
resolve(ok);
|
|
276
|
+
};
|
|
277
|
+
socket.once("connect", () => done(true));
|
|
278
|
+
socket.once("error", () => done(false));
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
async function waitForBackend(backendUrl) {
|
|
282
|
+
const s = spinner();
|
|
283
|
+
s.start(`Waiting for backend at ${backendUrl}`);
|
|
284
|
+
const deadline = Date.now() + 120_000;
|
|
285
|
+
while (Date.now() < deadline) {
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch(backendUrl, { signal: AbortSignal.timeout(2000) });
|
|
288
|
+
if (response.status < 500) {
|
|
289
|
+
s.stop(`Backend is responding ✓`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
// not ready yet
|
|
295
|
+
}
|
|
296
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
297
|
+
}
|
|
298
|
+
s.stop(`Backend did not become ready in 120s ✗`);
|
|
299
|
+
throw new Error(`Backend at ${backendUrl} never started responding. Check logs with: docker compose logs backend`);
|
|
300
|
+
}
|
|
301
|
+
main().catch(err => {
|
|
302
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
305
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-terse",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Scaffold and launch a self-hosted Terse instance with one command.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"terse",
|
|
8
|
+
"create-terse",
|
|
9
|
+
"scaffold",
|
|
10
|
+
"self-hosted",
|
|
11
|
+
"ai",
|
|
12
|
+
"workflow",
|
|
13
|
+
"docker-compose"
|
|
14
|
+
],
|
|
15
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/TerseAI/Terse.git",
|
|
22
|
+
"directory": "packages/create-terse"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://docs.useterse.ai",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/TerseAI/Terse/issues"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"create-terse": "dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"templates"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@clack/prompts": "^1.0.0",
|
|
37
|
+
"chalk": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"dev": "tsc --watch --preserveWatchOutput",
|
|
46
|
+
"install-global": "npm run build && npm link",
|
|
47
|
+
"format": "pnpm exec prettier --config ../../.prettierrc --write \"src/**/*.ts\"",
|
|
48
|
+
"format:check": "pnpm exec prettier --config ../../.prettierrc --check \"src/**/*.ts\""
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Terse (self-hosted)
|
|
2
|
+
|
|
3
|
+
You're running a self-hosted Terse instance. This directory holds everything needed to operate it:
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
docker-compose.yml # service definitions (postgres, backend, frontend)
|
|
7
|
+
.env # all configuration (URLs, ports, secrets) — edit here
|
|
8
|
+
README.md # you are here
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Daily operations
|
|
12
|
+
|
|
13
|
+
| Goal | Command |
|
|
14
|
+
| ----------------------------- | ------------------------------------ |
|
|
15
|
+
| Open the UI | visit the `FRONTEND_URL` from `.env` |
|
|
16
|
+
| Tail logs (all services) | `docker compose logs -f` |
|
|
17
|
+
| Tail backend only | `docker compose logs -f backend` |
|
|
18
|
+
| Restart after editing `.env` | `docker compose up -d` |
|
|
19
|
+
| Stop everything | `docker compose down` |
|
|
20
|
+
| Stop and delete all data | `docker compose down -v` |
|
|
21
|
+
| Pull the latest image | `docker compose pull && docker compose up -d` |
|
|
22
|
+
| Open a shell in the backend | `docker compose exec backend sh` |
|
|
23
|
+
| Check service status | `docker compose ps` |
|
|
24
|
+
|
|
25
|
+
## What's running
|
|
26
|
+
|
|
27
|
+
- **postgres** — Postgres 16, data persisted in the `terse_postgres` Docker volume.
|
|
28
|
+
- **backend** — Terse API, port from `BACKEND_URL` in `.env`. SQLite for local secrets lives in the `terse_sqlite` volume.
|
|
29
|
+
- **frontend** — Vite dev server, port from `FRONTEND_URL` in `.env`.
|
|
30
|
+
|
|
31
|
+
Both backend and frontend run from the same prebuilt image: `us-central1-docker.pkg.dev/fluid-analogy-473415-c2/public/terse:latest`. To pin to a specific tag, uncomment `TERSE_IMAGE=` at the bottom of `.env`.
|
|
32
|
+
|
|
33
|
+
## Configuration
|
|
34
|
+
|
|
35
|
+
Everything user-tunable lives in `.env`:
|
|
36
|
+
|
|
37
|
+
- **URLs** — `FRONTEND_URL` / `BACKEND_URL` must match what your browser sees. If you put Terse behind a reverse proxy or a real domain, change both and restart.
|
|
38
|
+
- **Postgres** — credentials and host port. Change before first start; changing later requires `docker compose down -v` (destroys data).
|
|
39
|
+
- **Secrets** — `JWT_SECRET` and `LOCAL_SECRETS_ENCRYPTION_KEY` are generated for you. Rotating `LOCAL_SECRETS_ENCRYPTION_KEY` invalidates any stored integration credentials.
|
|
40
|
+
|
|
41
|
+
After any edit, run `docker compose up -d` to apply.
|
|
42
|
+
|
|
43
|
+
## Connecting integrations
|
|
44
|
+
|
|
45
|
+
All integration config lives in this same `.env`. There's no second config file and no per-integration secrets store. After editing, `docker compose up -d` restarts the affected containers and the integration appears in the dashboard.
|
|
46
|
+
|
|
47
|
+
- **API-key integrations** — Datadog, Snowflake, HeyReach, LaunchDarkly, PostHog. No env vars needed; paste the API key in the dashboard at connect time.
|
|
48
|
+
- **OAuth integrations** — Gmail, GitHub, Slack, Linear, Notion, Attio. Gated by env vars. Every required `<PROVIDER>_CLIENT_ID` / `_CLIENT_SECRET` / `_REDIRECT_URI` (names vary slightly per provider) must be set, otherwise the integration is hidden.
|
|
49
|
+
|
|
50
|
+
Callback URLs (`_REDIRECT_URI`, `_OAUTH_CALLBACK_URL`, etc.) must be reachable from the user's browser. For a default local install that's `http://localhost:3001/<provider>/callback`. If the OAuth provider rejects `localhost`, expose the backend via a tunnel (ngrok, Cloudflare Tunnel) and point the callback URL at the public hostname.
|
|
51
|
+
|
|
52
|
+
The full env-var list per integration is in [docs.useterse.ai/self-hosting-control-plane](https://docs.useterse.ai/self-hosting-control-plane#integrations).
|
|
53
|
+
|
|
54
|
+
## The `terse` CLI
|
|
55
|
+
|
|
56
|
+
The installer also globally installs `terse-cli` (`npm i -g terse-cli`) and points it at this instance. From any directory:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
terse target # show which backend the CLI talks to
|
|
60
|
+
terse init my-job # scaffold a new job
|
|
61
|
+
terse deploy # deploy a job to your instance
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
CLI reference: [docs.useterse.ai/reference/cli](https://docs.useterse.ai/reference/cli).
|
|
65
|
+
|
|
66
|
+
## Upgrading
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
docker compose pull
|
|
70
|
+
docker compose up -d
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The `latest` tag is updated on every push to `main` in the [TerseAI/Terse](https://github.com/TerseAI/Terse) repo. Pin `TERSE_IMAGE` in `.env` if you want explicit control over upgrades.
|
|
74
|
+
|
|
75
|
+
## Backups
|
|
76
|
+
|
|
77
|
+
The only stateful data lives in two Docker volumes:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
docker volume ls | grep terse
|
|
81
|
+
# terse_postgres ← Postgres data
|
|
82
|
+
# terse_sqlite ← local secrets DB
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Back them up with `docker run --rm -v terse_postgres:/data -v $PWD:/backup alpine tar czf /backup/postgres.tar.gz -C /data .` (and similarly for `terse_sqlite`).
|
|
86
|
+
|
|
87
|
+
## Resources
|
|
88
|
+
|
|
89
|
+
- **Docs** — [docs.useterse.ai](https://docs.useterse.ai)
|
|
90
|
+
- **Quickstart** — [docs.useterse.ai/quickstart](https://docs.useterse.ai/quickstart)
|
|
91
|
+
- **Self-hosting guide** — [docs.useterse.ai/self-hosting](https://docs.useterse.ai/self-hosting)
|
|
92
|
+
- **SDK reference** — [docs.useterse.ai/reference/typescript-sdk](https://docs.useterse.ai/reference/typescript-sdk)
|
|
93
|
+
- **Website** — [useterse.ai](https://useterse.ai)
|
|
94
|
+
- **Source** — [github.com/TerseAI/Terse](https://github.com/TerseAI/Terse)
|
|
95
|
+
|
|
96
|
+
## Getting help
|
|
97
|
+
|
|
98
|
+
- **Bugs / feature requests** — open an issue at [github.com/TerseAI/Terse/issues](https://github.com/TerseAI/Terse/issues). Include `docker compose logs` output and the relevant section of `.env` (with secrets redacted).
|
|
99
|
+
- **Questions** — check [docs.useterse.ai](https://docs.useterse.ai) first, then open a Discussion at [github.com/TerseAI/Terse/discussions](https://github.com/TerseAI/Terse/discussions).
|
|
100
|
+
|
|
101
|
+
## Contributing
|
|
102
|
+
|
|
103
|
+
Terse code is available on [Github](https://github.com/TerseAI/Terse). The contribution guide lives in the repo at [CONTRIBUTING.md](https://github.com/TerseAI/Terse/blob/main/CONTRIBUTING.md). PRs welcome for bug fixes, integrations, and docs improvements.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
container_name: terse-postgres
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
environment:
|
|
7
|
+
POSTGRES_USER: ${POSTGRES_USER:-postgres}
|
|
8
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
|
|
9
|
+
POSTGRES_DB: ${POSTGRES_DB:-terse}
|
|
10
|
+
ports:
|
|
11
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
12
|
+
volumes:
|
|
13
|
+
- terse_postgres:/var/lib/postgresql/data
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
|
|
16
|
+
interval: 5s
|
|
17
|
+
timeout: 5s
|
|
18
|
+
retries: 10
|
|
19
|
+
|
|
20
|
+
backend:
|
|
21
|
+
image: ${TERSE_IMAGE:-us-central1-docker.pkg.dev/fluid-analogy-473415-c2/public/terse:latest}
|
|
22
|
+
pull_policy: always
|
|
23
|
+
container_name: terse-backend
|
|
24
|
+
restart: unless-stopped
|
|
25
|
+
working_dir: /workspace/backend
|
|
26
|
+
depends_on:
|
|
27
|
+
postgres:
|
|
28
|
+
condition: service_healthy
|
|
29
|
+
env_file:
|
|
30
|
+
- ./.env
|
|
31
|
+
environment:
|
|
32
|
+
DATABASE_URL: postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@postgres:5432/${POSTGRES_DB:-terse}
|
|
33
|
+
LOCAL_DB_URL: file:/data/local.db
|
|
34
|
+
ports:
|
|
35
|
+
- "${BACKEND_PORT:-3001}:3001"
|
|
36
|
+
volumes:
|
|
37
|
+
- terse_sqlite:/data
|
|
38
|
+
command: >
|
|
39
|
+
sh -c "pnpm exec prisma migrate deploy &&
|
|
40
|
+
pnpm exec prisma migrate deploy --schema=./prisma/local/schema.prisma &&
|
|
41
|
+
pnpm run dev:server"
|
|
42
|
+
|
|
43
|
+
frontend:
|
|
44
|
+
image: ${TERSE_IMAGE:-us-central1-docker.pkg.dev/fluid-analogy-473415-c2/public/terse:latest}
|
|
45
|
+
pull_policy: always
|
|
46
|
+
container_name: terse-frontend
|
|
47
|
+
restart: unless-stopped
|
|
48
|
+
working_dir: /workspace/frontend
|
|
49
|
+
depends_on:
|
|
50
|
+
- backend
|
|
51
|
+
env_file:
|
|
52
|
+
- ./.env
|
|
53
|
+
ports:
|
|
54
|
+
- "${FRONTEND_PORT:-5173}:5173"
|
|
55
|
+
command: pnpm exec vite --host 0.0.0.0 --port 5173
|
|
56
|
+
|
|
57
|
+
volumes:
|
|
58
|
+
terse_postgres:
|
|
59
|
+
terse_sqlite:
|