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.
Files changed (3) hide show
  1. package/README.md +63 -24
  2. package/dist/cli.mjs +27 -32
  3. 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/fttm"
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/fttm?style=flat-square"
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="docs/splash.png" alt="fttm — Fly to the Moon" width="800">
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
- ```sh
33
- $ fttm "reduce complexity of the codebase without changing functionality"
34
- # have a good sleep
35
- ```
32
+ ### Install
36
33
 
37
34
  ```sh
38
- $ fttm "reduce complexity of the codebase without changing functionality" \
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 with Specific Model
38
+ ### OpenCode
45
39
 
46
40
  ```sh
47
- $ fttm "implement a new feature" \
41
+ fttm "your task here" \
48
42
  --agent opencode \
49
43
  --model minimax-cn-coding-plan/MiniMax-M2.7
50
- # foreground mode, model displayed in UI
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
- $ fttm "implement a new feature" \
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
- Run `fttm` from inside a Git repository with a clean working tree. If you are starting from a plain directory, run `git init` first.
65
- `fttm` supports macOS, Linux, and Windows.
69
+ ### Skills
66
70
 
67
- ## Install
68
-
69
- **npm**
71
+ Install the `fttm` skill for interactive command generation:
70
72
 
71
73
  ```sh
72
- npm install -g fttm
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
- **From source**
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/YOUR_USERNAME/fttm.git
79
- cd fttm
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, isAbsolute, join, resolve } from "node:path";
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 excludePath = execFileSync("git", [
263
- "rev-parse",
264
- "--git-path",
265
- "info/exclude"
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 on an objective given below.
2116
- This is iteration ${params.n} of an ongoing loop to fully accomplish the objective.
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. Focus on the next smallest logical unit of work that's individually testable and would make incremental progress towards the objective - that's the scope of this iteration.
2122
- 3. If you made code changes, run build/tests/linters/formatters if available to validate your work.
2123
- 4. Do NOT make any git commits. Commits will be handled automatically by the fttm orchestrator.
2124
- 5. When you are done, respond with a JSON object according to the provided schema.
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 complete your iteration. set to false only if something made it impossible for you to do your work
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 and weren't captured by previous notes
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, modelName) {
2918
- const hint = RESUME_HINT;
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
- frame.push(renderResumeHintCells(terminalWidth, modelName));
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.12",
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/fttm"
46
+ "url": "https://github.com/evoerax/fly-to-the-moon"
47
47
  },
48
48
  "engines": {
49
49
  "node": ">=20"