fly-to-moon 0.1.12 → 0.1.14
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 +63 -24
- package/dist/cli.mjs +27 -32
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
<h1 align="center">Fly to the Moon, Fly to Mars</h1>
|
|
3
3
|
|
|
4
4
|
<p align="center">
|
|
5
|
-
<a href="https://www.npmjs.com/package/
|
|
5
|
+
<a href="https://www.npmjs.com/package/fly-to-moon"
|
|
6
6
|
><img
|
|
7
7
|
alt="npm"
|
|
8
|
-
src="https://img.shields.io/npm/v/
|
|
8
|
+
src="https://img.shields.io/npm/v/fly-to-moon?style=flat-square"
|
|
9
9
|
/></a>
|
|
10
10
|
<a
|
|
11
11
|
href="https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-blue?style=flat-square"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<img src="
|
|
19
|
+
<!-- <img src="assets/splash.png" alt="fttm — Fly to the Moon" width="800"> -->
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
22
|
Never wake up empty-handed.
|
|
@@ -29,31 +29,36 @@ fttm is an autonomous coding agent orchestrator — each iteration makes one sma
|
|
|
29
29
|
|
|
30
30
|
## Quick Start
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
$ fttm "reduce complexity of the codebase without changing functionality"
|
|
34
|
-
# have a good sleep
|
|
35
|
-
```
|
|
32
|
+
### Install
|
|
36
33
|
|
|
37
34
|
```sh
|
|
38
|
-
|
|
39
|
-
--max-iterations 10 \
|
|
40
|
-
--max-tokens 5000000
|
|
41
|
-
# have a good nap
|
|
35
|
+
npm install -g fly-to-moon
|
|
42
36
|
```
|
|
43
37
|
|
|
44
|
-
### OpenCode
|
|
38
|
+
### OpenCode
|
|
45
39
|
|
|
46
40
|
```sh
|
|
47
|
-
|
|
41
|
+
fttm "your task here" \
|
|
48
42
|
--agent opencode \
|
|
49
43
|
--model minimax-cn-coding-plan/MiniMax-M2.7
|
|
50
|
-
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Claude Code
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
fttm "your task here" --agent claude
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Codex
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
fttm "your task here" --agent codex
|
|
51
56
|
```
|
|
52
57
|
|
|
53
58
|
### Background/Daemon Mode
|
|
54
59
|
|
|
55
60
|
```sh
|
|
56
|
-
|
|
61
|
+
fttm "your task here" \
|
|
57
62
|
--agent opencode \
|
|
58
63
|
--model minimax-cn-coding-plan/MiniMax-M2.7 \
|
|
59
64
|
--max-iterations 50 \
|
|
@@ -61,22 +66,28 @@ $ fttm "implement a new feature" \
|
|
|
61
66
|
# runs in background, returns immediately to terminal
|
|
62
67
|
```
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
`fttm` supports macOS, Linux, and Windows.
|
|
69
|
+
### Skills
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
**npm**
|
|
71
|
+
Install the `fttm` skill for interactive command generation:
|
|
70
72
|
|
|
71
73
|
```sh
|
|
72
|
-
|
|
74
|
+
npx skills add evoerax/fly-to-the-moon --skill fttm
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then use it:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
fttm skill -> interactive fttm command builder
|
|
73
81
|
```
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
Run `fttm` from inside a Git repository with a clean working tree. If you are starting from a plain directory, run `git init` first.
|
|
84
|
+
`fttm` supports macOS, Linux, and Windows.
|
|
85
|
+
|
|
86
|
+
## Install (From source)
|
|
76
87
|
|
|
77
88
|
```sh
|
|
78
|
-
git clone https://github.com/
|
|
79
|
-
cd
|
|
89
|
+
git clone https://github.com/evoerax/fly-to-the-moon.git
|
|
90
|
+
cd fly-to-the-moon
|
|
80
91
|
npm install
|
|
81
92
|
npm run build
|
|
82
93
|
npm link
|
|
@@ -191,6 +202,34 @@ agentPathOverride:
|
|
|
191
202
|
Paths may be absolute, bare executable names already on your `PATH`, `~`-prefixed, or relative to the config directory (`~/.fttm/`). The override replaces only the binary name; all standard arguments are preserved, so the replacement must be CLI-compatible with the original agent. On Windows, `.cmd` and `.bat` wrappers are supported, including bare names resolved from `PATH`. For `rovodev`, the override must point to an `acli`-compatible binary since fttm invokes it as `<bin> rovodev serve ...`.
|
|
192
203
|
When sleep prevention is enabled, `fttm` uses the native mechanism for your OS: `caffeinate` on macOS, `systemd-inhibit` on Linux, and a small PowerShell helper backed by `SetThreadExecutionState` on Windows.
|
|
193
204
|
|
|
205
|
+
### Sleep Prevention
|
|
206
|
+
|
|
207
|
+
By default, `fttm` prevents your computer from sleeping while running:
|
|
208
|
+
|
|
209
|
+
| OS | Command | What it does |
|
|
210
|
+
| ------- | -------------------------------------- | ------------------------------------ |
|
|
211
|
+
| macOS | `caffeinate -i -w <pid>` | Prevents idle sleep until fttm exits |
|
|
212
|
+
| Linux | `systemd-inhibit` | Blocks sleep via systemd logind |
|
|
213
|
+
| Windows | PowerShell + `SetThreadExecutionState` | Calls Windows sleep blocker API |
|
|
214
|
+
|
|
215
|
+
You can disable this with `--prevent-sleep off`.
|
|
216
|
+
|
|
217
|
+
## Local Run Metadata
|
|
218
|
+
|
|
219
|
+
`fttm` stores run data in `.fttm/runs/<run-id>/` within your project:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
.fttm/
|
|
223
|
+
└── runs/
|
|
224
|
+
└── <run-id>/
|
|
225
|
+
├── notes.md # Iteration history (cumulative)
|
|
226
|
+
├── prompt.md # Original prompt
|
|
227
|
+
├── base-commit # Git commit hash where run started
|
|
228
|
+
└── output-schema.json # JSON schema for agent output
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This folder is automatically added to `.gitignore` — it never gets committed.
|
|
232
|
+
|
|
194
233
|
## Debug Logs
|
|
195
234
|
|
|
196
235
|
Set `FTtm_DEBUG_LOG_PATH` to capture lifecycle events as JSONL while debugging a run:
|
package/dist/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { appendFileSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, rmdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { basename, dirname,
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
5
|
import process$1 from "node:process";
|
|
6
6
|
import { createInterface } from "node:readline";
|
|
7
7
|
import { Command, InvalidArgumentError } from "commander";
|
|
@@ -259,22 +259,11 @@ function writeSchemaFile(schemaPath) {
|
|
|
259
259
|
writeFileSync(schemaPath, JSON.stringify(AGENT_OUTPUT_SCHEMA, null, 2), "utf-8");
|
|
260
260
|
}
|
|
261
261
|
function ensureRunMetadataIgnored(cwd) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
cwd,
|
|
268
|
-
encoding: "utf-8"
|
|
269
|
-
}).trim();
|
|
270
|
-
const resolved = isAbsolute(excludePath) ? excludePath : join(cwd, excludePath);
|
|
271
|
-
const entry = ".fttm/runs/";
|
|
272
|
-
mkdirSync(dirname(resolved), { recursive: true });
|
|
273
|
-
if (existsSync(resolved)) {
|
|
274
|
-
const content = readFileSync(resolved, "utf-8");
|
|
275
|
-
if (content.split("\n").some((line) => line.trim() === entry)) return;
|
|
276
|
-
appendFileSync(resolved, `${content.length > 0 && !content.endsWith("\n") ? "\n" : ""}${entry}\n`, "utf-8");
|
|
277
|
-
} else writeFileSync(resolved, `${entry}\n`, "utf-8");
|
|
262
|
+
const ftmDir = join(cwd, ".fttm");
|
|
263
|
+
const gitignorePath = join(ftmDir, ".gitignore");
|
|
264
|
+
if (existsSync(gitignorePath)) return;
|
|
265
|
+
mkdirSync(ftmDir, { recursive: true });
|
|
266
|
+
writeFileSync(gitignorePath, "*\n", "utf-8");
|
|
278
267
|
}
|
|
279
268
|
function setupRun(runId, prompt, baseCommit, cwd) {
|
|
280
269
|
ensureRunMetadataIgnored(cwd);
|
|
@@ -2112,23 +2101,23 @@ function createAgent(name, runInfo, pathOverride, model) {
|
|
|
2112
2101
|
//#endregion
|
|
2113
2102
|
//#region src/templates/iteration-prompt.ts
|
|
2114
2103
|
function buildIterationPrompt(params) {
|
|
2115
|
-
return `You are working autonomously
|
|
2116
|
-
This is iteration ${params.n}
|
|
2104
|
+
return `You are working autonomously towards an objective given below.
|
|
2105
|
+
This is iteration ${params.n}. Each iteration aims to make an incremental step forward, not to complete the entire objective.
|
|
2117
2106
|
|
|
2118
2107
|
## Instructions
|
|
2119
2108
|
|
|
2120
|
-
1. Read .fttm/runs/${params.runId}/notes.md first to understand what has been done in previous iterations
|
|
2121
|
-
2.
|
|
2122
|
-
3. If you
|
|
2123
|
-
4. Do NOT make any git commits
|
|
2124
|
-
|
|
2109
|
+
1. Read .fttm/runs/${params.runId}/notes.md first to understand what has been done in previous iterations
|
|
2110
|
+
2. Identify the next smallest logical unit of work that's individually verifiable and would make incremental progress towards the objective, and treat that as the scope of this iteration
|
|
2111
|
+
3. If you attempted a solution and it didn't end up moving the needle on the objective, document learnings and record success=false, then conclude the iteration rather than continuously pivoting
|
|
2112
|
+
4. If you made code changes, run build/tests/linters/formatters if available to validate your work. Do NOT make any git commits - that will be handled automatically by the fttm orchestrator
|
|
2113
|
+
6. Finally, respond with a JSON object according to the provided schema
|
|
2125
2114
|
|
|
2126
2115
|
## Output
|
|
2127
2116
|
|
|
2128
|
-
- success: whether you were able to
|
|
2117
|
+
- success: whether you were able to make a meaningful contribution that got us closer towards the objective. setting this to false means any code change you made should be discarded
|
|
2129
2118
|
- summary: a concise one-sentence summary of the accomplishment in this iteration
|
|
2130
2119
|
- key_changes_made: an array of descriptions for key changes you made. don't group this by file - group by logical units of work. don't describe activities - describe material outcomes
|
|
2131
|
-
- key_learnings: an array of new learnings that were surprising
|
|
2120
|
+
- key_learnings: an array of new learnings that were surprising, weren't captured by previous notes and would be informative for future iterations
|
|
2132
2121
|
|
|
2133
2122
|
## Objective
|
|
2134
2123
|
|
|
@@ -2149,6 +2138,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2149
2138
|
activeIterationPromise = null;
|
|
2150
2139
|
activeAbortController = null;
|
|
2151
2140
|
pendingAbortReason = null;
|
|
2141
|
+
loopDone = false;
|
|
2152
2142
|
state = {
|
|
2153
2143
|
status: "running",
|
|
2154
2144
|
currentIteration: 0,
|
|
@@ -2182,6 +2172,10 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2182
2172
|
stop() {
|
|
2183
2173
|
this.stopRequested = true;
|
|
2184
2174
|
this.activeAbortController?.abort();
|
|
2175
|
+
if (this.loopDone) {
|
|
2176
|
+
this.emit("stopped");
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2185
2179
|
if (this.stopPromise) return;
|
|
2186
2180
|
this.stopPromise = (async () => {
|
|
2187
2181
|
if (this.activeIterationPromise) {
|
|
@@ -2265,6 +2259,7 @@ var Orchestrator = class extends EventEmitter {
|
|
|
2265
2259
|
this.activeIterationPromise = null;
|
|
2266
2260
|
if (this.stopPromise) await this.stopPromise;
|
|
2267
2261
|
else await this.closeAgent();
|
|
2262
|
+
this.loopDone = true;
|
|
2268
2263
|
}
|
|
2269
2264
|
}
|
|
2270
2265
|
async runIteration(prompt) {
|
|
@@ -2789,6 +2784,7 @@ const MOON_PHASE_PERIOD = 1600;
|
|
|
2789
2784
|
const MAX_MSG_LINES = 3;
|
|
2790
2785
|
const MAX_MSG_LINE_LEN = CONTENT_WIDTH;
|
|
2791
2786
|
const RESUME_HINT = "[ctrl+c to stop, fttm again to resume]";
|
|
2787
|
+
const DONE_HINT = "[ctrl+c to exit]";
|
|
2792
2788
|
function spacedLabel(text) {
|
|
2793
2789
|
return text.split("").join(" ");
|
|
2794
2790
|
}
|
|
@@ -2914,10 +2910,8 @@ function centerLineCells(content, width) {
|
|
|
2914
2910
|
...emptyCells(rightPad)
|
|
2915
2911
|
];
|
|
2916
2912
|
}
|
|
2917
|
-
function renderResumeHintCells(width,
|
|
2918
|
-
|
|
2919
|
-
if (modelName) return centerLineCells(textToCells(`${modelName} ${hint}`, "dim"), width);
|
|
2920
|
-
return centerLineCells(textToCells(hint, "dim"), width);
|
|
2913
|
+
function renderResumeHintCells(width, done) {
|
|
2914
|
+
return centerLineCells(textToCells(done ? DONE_HINT : RESUME_HINT, "dim"), width);
|
|
2921
2915
|
}
|
|
2922
2916
|
/**
|
|
2923
2917
|
* Builds the centered content viewport for the renderer.
|
|
@@ -3025,7 +3019,8 @@ function buildFrameCells(prompt, agentName, modelName, state, topStars, bottomSt
|
|
|
3025
3019
|
]);
|
|
3026
3020
|
}
|
|
3027
3021
|
for (let y = 0; y < bottomHeight; y++) frame.push(renderStarLineCells(bottomStars, terminalWidth, y, now));
|
|
3028
|
-
|
|
3022
|
+
const isDone = state.status === "aborted";
|
|
3023
|
+
frame.push(renderResumeHintCells(terminalWidth, isDone));
|
|
3029
3024
|
frame.push(emptyCells(terminalWidth));
|
|
3030
3025
|
return frame;
|
|
3031
3026
|
}
|
|
@@ -3327,7 +3322,7 @@ program.name("fttm").description("Fly to the moon, fly to the mars - AI agents f
|
|
|
3327
3322
|
process$1.on("SIGINT", handleSigInt);
|
|
3328
3323
|
process$1.on("SIGTERM", handleSigTerm);
|
|
3329
3324
|
const orchestratorPromise = orchestrator.start().finally(() => {
|
|
3330
|
-
renderer.stop();
|
|
3325
|
+
if (!(orchestrator.getState().status === "aborted" && process$1.stdin.isTTY)) renderer.stop();
|
|
3331
3326
|
}).catch((err) => {
|
|
3332
3327
|
exitAltScreen();
|
|
3333
3328
|
die(err instanceof Error ? err.message : String(err));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fly-to-moon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Humans fly to space. AI does the work. — Fly to the moon, fly to Mars",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
46
|
-
"url": "https://github.com/evoerax/
|
|
46
|
+
"url": "https://github.com/evoerax/fly-to-the-moon"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=20"
|