entropy-machines 0.1.2 → 0.1.3

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.
@@ -47,6 +47,15 @@ const NPM_ENTRY = path.basename(__filename);
47
47
 
48
48
  const DEFAULT_DIR = 'entropy-machines';
49
49
 
50
+ // ANSI color, gated on tty + NO_COLOR (https://no-color.org/).
51
+ const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
52
+ const BOLD = USE_COLOR ? '\x1b[1m' : '';
53
+ const DIM = USE_COLOR ? '\x1b[2m' : '';
54
+ const GREEN = USE_COLOR ? '\x1b[32m' : '';
55
+ const YELLOW = USE_COLOR ? '\x1b[33m' : '';
56
+ const CYAN = USE_COLOR ? '\x1b[36m' : '';
57
+ const RESET = USE_COLOR ? '\x1b[0m' : '';
58
+
50
59
  function die(lines) {
51
60
  for (const line of [].concat(lines)) console.error(line);
52
61
  process.exit(2);
@@ -196,31 +205,31 @@ function init(argv) {
196
205
  }
197
206
 
198
207
  const shown = path.relative(root, dest) || '.';
199
- console.log(`entropy-machines ${PKG.version}: vendored into ${dest}`);
200
- for (const d of HARNESS_DIRS) console.log(` ${path.join(shown, d)}/`);
201
- console.log(` ${path.join(shown, 'LICENSE')}${licenseNote}`);
208
+ console.log(`${GREEN}entropy-machines ${PKG.version}: vendored into ${dest}${RESET}`);
209
+ for (const d of HARNESS_DIRS) console.log(` ${DIM}${path.join(shown, d)}/${RESET}`);
210
+ console.log(` ${DIM}${path.join(shown, 'LICENSE')}${licenseNote}${RESET}`);
202
211
  console.log('');
203
212
 
204
213
  // ---- Hand over to the harness. Everything past here is shell + Python. ----
205
214
 
206
215
  const vendoredInit = path.join(dest, 'bin', 'init');
207
- console.log(`entropy-machines: running ${path.join(shown, 'bin', 'init')}`);
216
+ console.log(`entropy-machines: running ${DIM}${path.join(shown, 'bin', 'init')}${RESET}`);
208
217
  const r = spawnSync(vendoredInit, [], { cwd: root, stdio: 'inherit' });
209
218
  if (r.error) {
210
- console.error(`entropy-machines: could not run ${vendoredInit}: ${r.error.message}`);
211
- console.error(' The files ARE vendored. Run it yourself from the repo root.');
219
+ console.error(`${YELLOW}entropy-machines: could not run ${vendoredInit}: ${r.error.message}${RESET}`);
220
+ console.error(` The files ARE vendored. Run it yourself from the repo root.`);
212
221
  process.exit(2);
213
222
  }
214
223
  if (r.status !== 0) {
215
224
  console.error('');
216
- console.error('entropy-machines: the files are vendored, but bin/init did not');
225
+ console.error(`${YELLOW}entropy-machines: the files are vendored, but bin/init did not${RESET}`);
217
226
  console.error(' finish. Fix what it named above and re-run it from the repo');
218
- console.error(` root: ${path.join(shown, 'bin', 'init')}`);
227
+ console.error(` root: ${CYAN}${path.join(shown, 'bin', 'init')}${RESET}`);
219
228
  process.exit(r.status === null ? 2 : r.status);
220
229
  }
221
230
 
222
231
  console.log('');
223
- console.log('entropy-machines: done. Next —');
232
+ console.log(`${BOLD}entropy-machines: done. Next —${RESET}`);
224
233
  // Named paths only, never a guessed docs directory — that one is config
225
234
  // (docs.dir), and CONFIG.md rule 1 makes a hardcoded path in bin/ a bug.
226
235
  // NEVER `git add .` for the root layout. This command is reached over npm,
@@ -231,9 +240,12 @@ function init(argv) {
231
240
  shown === '.'
232
241
  ? `${HARNESS_DIRS.join(' ')} LICENSE .gitignore`
233
242
  : `${shown} .gitignore`;
234
- console.log(` 1. commit what init just wrote: git add ${addPaths}`);
235
- console.log(` 2. point your coding agent at ${path.join(shown, 'docs', 'AGENT-QUICKSTART.md')}`);
236
- console.log(` 3. ${path.join(shown, 'bin', 'serve')} — answer the PRD it opens`);
243
+ // Step 1: agent context (most important the agent needs this first)
244
+ console.log(` ${BOLD}1. point your coding agent at ${CYAN}${path.join(shown, 'docs', 'AGENT-QUICKSTART.md')}${RESET}`);
245
+ // Step 2: run serve to get the PRD
246
+ console.log(` 2. ${CYAN}${path.join(shown, 'bin', 'serve')}${RESET} — answer the PRD it opens (creates issues)`);
247
+ // Step 3: commit (least urgent)
248
+ console.log(` ${DIM}3. commit what init wrote: git add ${addPaths}${RESET}`);
237
249
  }
238
250
 
239
251
  function findHarness(root) {
@@ -262,7 +274,7 @@ function start(argv) {
262
274
  if (!fs.existsSync(serve)) {
263
275
  die([`entropy-machines: ${path.join(loc.shown, 'bin', 'serve')} not found.`]);
264
276
  }
265
- console.log(`\nentropy-machines: starting serve from ${loc.shown}/\n`);
277
+ console.log(`\n${BOLD}entropy-machines: starting serve from ${loc.shown}/${RESET}\n`);
266
278
  const r = spawnSync('sh', [serve], { cwd: loc.dir, stdio: 'inherit' });
267
279
  process.exit(r.status ?? 1);
268
280
  }
package/bin/init CHANGED
@@ -18,6 +18,13 @@
18
18
  # --force overwrite an existing config.json.
19
19
  set -e
20
20
 
21
+ # ---- ANSI color, gated on tty + NO_COLOR (https://no-color.org/) ----
22
+ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
23
+ BOLD='\033[1m' DIM='\033[2m' GREEN='\033[32m' YELLOW='\033[33m' CYAN='\033[36m' RESET='\033[0m'
24
+ else
25
+ BOLD='' DIM='' GREEN='' YELLOW='' CYAN='' RESET=''
26
+ fi
27
+
21
28
  usage() {
22
29
  cat >&2 <<'EOF'
23
30
  usage: bin/init [--force]
@@ -195,18 +202,18 @@ fi
195
202
  # in fact everything was — and a plain re-run then died on "config.json
196
203
  # already exists" with no repair path named.
197
204
  if ! (cd "$project" && "$ENTROPY_MACHINES_HOME/bin/tracker" ready >/dev/null 2>&1); then
198
- echo "init: WARNING — $target was WRITTEN, but bin/tracker could not read the" >&2
205
+ printf "${YELLOW}init: WARNING — $target was WRITTEN, but bin/tracker could not read the${RESET}\n" >&2
199
206
  echo " tracker back afterward. This is not a refusal: everything above" >&2
200
207
  echo " really was written, and the project is half-initialised." >&2
201
- echo " Check $project/.entropy-machines/ and $target by hand." >&2
208
+ printf " Check ${DIM}$project/.entropy-machines/${RESET} and ${DIM}$target${RESET} by hand.\n" >&2
202
209
  echo " To re-run once you have fixed it (a plain re-run will refuse," >&2
203
210
  echo " because config.json now exists):" >&2
204
- echo " $ENTROPY_MACHINES_HOME/bin/init --force" >&2
211
+ printf " ${CYAN}$ENTROPY_MACHINES_HOME/bin/init --force${RESET}\n" >&2
205
212
  exit 2
206
213
  fi
207
214
 
208
- echo "init: wrote $target"
209
- echo "$prd_note"
215
+ printf "${GREEN}init: wrote $target${RESET}\n"
216
+ printf "${GREEN}$prd_note${RESET}\n"
210
217
  if [ -n "$prd_written" ]; then
211
218
  echo " questions in it are the ones only you can answer."
212
219
  fi
@@ -225,8 +232,8 @@ esac
225
232
  # the first document the harness hands them would otherwise hit a 404 — which
226
233
  # is exactly the bug that made this necessary.
227
234
  "$ENTROPY_MACHINES_HOME/bin/tracker" render >/dev/null 2>&1 || \
228
- echo "init: warning — could not render TRACKER.html; run \`$cmd_prefix/tracker render\`" >&2
235
+ printf "${YELLOW}init: warning — could not render TRACKER.html; run \`${CYAN}$cmd_prefix/tracker render${YELLOW}\`${RESET}\n" >&2
229
236
 
230
- echo "init: tracker is live and empty — \`$cmd_prefix/tracker ready\` answers."
231
- echo "init: next — run \`$cmd_prefix/serve\` and answer the PRD it opens."
237
+ printf "${GREEN}init: tracker is live and empty${RESET} — \`${CYAN}$cmd_prefix/tracker ready${RESET}\` answers.\n"
238
+ printf "${BOLD}init: next — run \`${CYAN}$cmd_prefix/serve${RESET}${BOLD}\` and answer the PRD it opens.${RESET}\n"
232
239
  echo " Filing the issues it produces is step one."
package/bin/serve CHANGED
@@ -690,18 +690,31 @@ class Handler(BaseHTTPRequestHandler):
690
690
 
691
691
 
692
692
  def main():
693
- try:
694
- server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler)
695
- except OSError as exc:
696
- if exc.errno == 48 or "Address already in use" in str(exc): # EADDRINUSE
697
- print(
698
- "serve: REFUSED — port %d is already in use.\n"
699
- " Another server (maybe a previous `bin/serve`) already holds it.\n"
700
- " Try a different port: bin/serve %d" % (PORT, PORT + 1),
701
- file=sys.stderr,
702
- )
693
+ # TRY THE REQUESTED PORT, THEN SCAN UPWARD. A user running `npx
694
+ # entropy-machines start` for the first time should not be stopped by a
695
+ # stale process on 8787 — the server picks the next open port and says
696
+ # which one it got. Cap at 20 attempts so a misconfigured machine gets a
697
+ # clear error, not an infinite loop.
698
+ MAX_ATTEMPTS = 20
699
+ port = PORT
700
+ server = None
701
+ for attempt in range(MAX_ATTEMPTS):
702
+ try:
703
+ server = ThreadingHTTPServer(("127.0.0.1", port), Handler)
704
+ break
705
+ except OSError as exc:
706
+ if exc.errno == 48 or "Address already in use" in str(exc):
707
+ if attempt == 0 and port == PORT:
708
+ print("serve: port %d busy, scanning for an open port…" % port,
709
+ file=sys.stderr, flush=True)
710
+ port += 1
711
+ continue
712
+ print("serve: REFUSED — could not bind 127.0.0.1:%d (%s)." % (port, exc),
713
+ file=sys.stderr)
703
714
  sys.exit(1)
704
- print("serve: REFUSED could not bind 127.0.0.1:%d (%s)." % (PORT, exc), file=sys.stderr)
715
+ if server is None:
716
+ print("serve: REFUSED — ports %d–%d all busy." % (PORT, PORT + MAX_ATTEMPTS - 1),
717
+ file=sys.stderr)
705
718
  sys.exit(1)
706
719
  # flush=True IS THE POINT OF THESE TWO LINES. Python block-buffers stdout
707
720
  # when it is not a tty — which is exactly the case a bootstrapping agent
@@ -710,7 +723,7 @@ def main():
710
723
  # never drained, because the very next statement blocks in serve_forever()
711
724
  # forever; the log stays 0 bytes while the server runs and 0 bytes after
712
725
  # it is killed. The URL is the one thing this command exists to produce.
713
- print("serving %s on http://localhost:%d" % (ENTROPY_MACHINES_ROOT, PORT), flush=True)
726
+ print("serving %s on http://localhost:%d" % (ENTROPY_MACHINES_ROOT, port), flush=True)
714
727
  print(" docs: %s (%s)" % (DOCS_PATH, DOCS_DIR), flush=True)
715
728
  print(" theme: %s (%s)" % (THEME_NAME, THEME_FILE), flush=True)
716
729
  try:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entropy-machines",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Vendors the entropy-machines agent-orchestration harness into your git repository as plain tracked files. A delivery mechanism, not a runtime — the harness is POSIX shell and Python 3 and needs no Node once the files have landed.",
5
5
  "license": "Elastic-2.0",
6
6
  "repository": {