@amenophis1er/foreman 0.1.3 → 0.1.4
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 +23 -0
- package/bin/foreman.mjs +1 -0
- package/package.json +1 -1
- package/src/cli.ts +11 -3
- package/src/server.ts +6 -0
- package/ui/dist/assets/index-D_tuMLCb.js +66 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-oXWBvBdD.js +0 -66
package/README.md
CHANGED
|
@@ -60,6 +60,19 @@ which account pays, the Claude Code install, Ollama and Codex if present, the
|
|
|
60
60
|
browser missions will use, the port, Tailscale, the data directory — and
|
|
61
61
|
exits. Nothing blocks unless it says so.
|
|
62
62
|
|
|
63
|
+
**Environment.** Everything the CLI reads; `foreman --help` prints the same list.
|
|
64
|
+
|
|
65
|
+
| Variable | Meaning | Default |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `PORT` | listen port | `4177` |
|
|
68
|
+
| `FOREMAN_HOME` | state directory: runs, settings, logs | `~/.foreman` |
|
|
69
|
+
| `FOREMAN_BIND` | `auto` (loopback + Tailscale when present), `local`, or `all` | `auto` |
|
|
70
|
+
| `FOREMAN_BROWSER` | browser for missions: `chrome`, `chromium`, `msedge`, `firefox` | `chrome` |
|
|
71
|
+
| `FOREMAN_CLAUDE_CONFIG_DIR` | the Claude Code install missions run under | inherited |
|
|
72
|
+
| `FOREMAN_CLAUDE_EXECUTABLE` | the Claude Code executable | bundled |
|
|
73
|
+
| `FOREMAN_AUTH_MODE` | assert `api-key` or `subscription`; fail at start on mismatch | unset |
|
|
74
|
+
| `FOREMAN_NO_TELEGRAM` | `1` to start without the Telegram bot: for a second server beside the main one, since Telegram allows one poller per bot | unset |
|
|
75
|
+
|
|
63
76
|
## A mission, start to finish
|
|
64
77
|
|
|
65
78
|
1. **Fleet.** The board: what needs you (answerable right there), what is
|
|
@@ -213,6 +226,16 @@ npm run dev # API + Vite together
|
|
|
213
226
|
scripts/dev-restart.sh # restarts the server only when nothing would be lost
|
|
214
227
|
```
|
|
215
228
|
|
|
229
|
+
To run a checkout beside an installed Foreman on the same machine, give it its
|
|
230
|
+
own port and leave the bot to the installed one:
|
|
231
|
+
|
|
232
|
+
```sh
|
|
233
|
+
PORT=4178 FOREMAN_BIND=local FOREMAN_NO_TELEGRAM=1 npm start
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Both read `~/.foreman`; look from the second, drive from the first — two
|
|
237
|
+
servers running missions against one store is the one case nothing guards.
|
|
238
|
+
|
|
216
239
|
Releases: `npm version <patch|minor|major> && git push --follow-tags` — CI
|
|
217
240
|
tests, publishes to npm with provenance, and creates the GitHub release.
|
|
218
241
|
|
package/bin/foreman.mjs
CHANGED
|
@@ -43,6 +43,7 @@ Environment:
|
|
|
43
43
|
FOREMAN_CLAUDE_CONFIG_DIR Claude Code install missions run under
|
|
44
44
|
FOREMAN_CLAUDE_EXECUTABLE Claude Code executable (default: bundled)
|
|
45
45
|
FOREMAN_AUTH_MODE Assert 'api-key' or 'subscription'; fail on mismatch
|
|
46
|
+
FOREMAN_NO_TELEGRAM=1 Do not attach the Telegram bot (a second server beside the main one)
|
|
46
47
|
`;
|
|
47
48
|
|
|
48
49
|
const [command = 'start', ...rest] = process.argv.slice(2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amenophis1er/foreman",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Autonomous mission runner on the Claude Agent SDK: a director plans, delegates to workers, verifies, and reports — from one dashboard, your phone, or the CLI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/src/cli.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* be up when the laptop lid is closed; this is that.
|
|
14
14
|
*/
|
|
15
15
|
import { execFile, spawn } from 'node:child_process';
|
|
16
|
-
import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
16
|
+
import { access, chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
17
17
|
import { openSync } from 'node:fs';
|
|
18
18
|
import os from 'node:os';
|
|
19
19
|
import path from 'node:path';
|
|
@@ -170,8 +170,12 @@ async function serviceUninstall(): Promise<number> {
|
|
|
170
170
|
/** Is the service registered, and is it running? Null pid when registered but idle. */
|
|
171
171
|
async function serviceState(): Promise<{ installed: boolean; running: boolean; pid?: number }> {
|
|
172
172
|
if (process.platform === 'darwin') {
|
|
173
|
+
// Installed means the plist is on disk; a stopped service (booted out
|
|
174
|
+
// by `service stop`) is still installed, and `service start` brings it
|
|
175
|
+
// back. launchctl only knows about loaded jobs, so it answers "running".
|
|
176
|
+
const installed = await access(path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`)).then(() => true, () => false);
|
|
173
177
|
const r = await sh('launchctl', ['print', `gui/${os.userInfo().uid}/${LABEL}`]);
|
|
174
|
-
if (r.code !== 0) return { installed
|
|
178
|
+
if (r.code !== 0) return { installed, running: false };
|
|
175
179
|
const pid = Number(/pid = (\d+)/.exec(r.out)?.[1]);
|
|
176
180
|
return { installed: true, running: Number.isFinite(pid) && pid > 0, ...(pid ? { pid } : {}) };
|
|
177
181
|
}
|
|
@@ -230,7 +234,11 @@ async function serviceRestart(): Promise<number> {
|
|
|
230
234
|
async function serviceStatus(): Promise<number> {
|
|
231
235
|
if (process.platform === 'darwin') {
|
|
232
236
|
const r = await sh('launchctl', ['print', `gui/${os.userInfo().uid}/${LABEL}`]);
|
|
233
|
-
if (r.code !== 0) {
|
|
237
|
+
if (r.code !== 0) {
|
|
238
|
+
const st = await serviceState();
|
|
239
|
+
console.log(st.installed ? 'Installed · stopped — `foreman service start` starts it (it also comes back at next login).' : 'Not installed. `foreman service install` keeps Foreman running.');
|
|
240
|
+
return 1;
|
|
241
|
+
}
|
|
234
242
|
const state = /state = (\w+)/.exec(r.out)?.[1] ?? 'unknown';
|
|
235
243
|
const pid = /pid = (\d+)/.exec(r.out)?.[1];
|
|
236
244
|
console.log(`Installed · ${state}${pid ? ` · pid ${pid}` : ''} · http://localhost:${PORT}`);
|
package/src/server.ts
CHANGED
|
@@ -608,6 +608,11 @@ let telegramBot: TelegramBot | null = null;
|
|
|
608
608
|
/** (Re)build the Telegram side from the stored token and linked chat. */
|
|
609
609
|
async function reattachTelegram(): Promise<boolean> {
|
|
610
610
|
notifyHub.detach('telegram');
|
|
611
|
+
// A second server beside the installed one (a dev checkout on another
|
|
612
|
+
// port) must not start a second poller: Telegram allows one per bot, and
|
|
613
|
+
// two fight over getUpdates. FOREMAN_NO_TELEGRAM=1 leaves the channel to
|
|
614
|
+
// whichever server does not set it.
|
|
615
|
+
if (process.env.FOREMAN_NO_TELEGRAM === '1') return false;
|
|
611
616
|
const s = await notifySettings();
|
|
612
617
|
const token = await getSecret(store.root, 'telegram');
|
|
613
618
|
if (!token) { void telegramBot?.stop(); telegramBot = null; return false; }
|
|
@@ -1348,6 +1353,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1348
1353
|
providerHasKey: await providerHasKeyOf(p),
|
|
1349
1354
|
activeRun: run ? { ...run.meta } : null,
|
|
1350
1355
|
lastRun: lastRun && {
|
|
1356
|
+
id: lastRun.id,
|
|
1351
1357
|
mission: lastRun.mission, title: lastRun.title, status: lastRun.status,
|
|
1352
1358
|
createdAt: lastRun.createdAt, costUsd: lastRun.costUsd,
|
|
1353
1359
|
// The card may print a dollar only where the dollar was real.
|