@junyoung-kim/reins 0.1.0 → 0.1.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/dist/cli.mjs CHANGED
@@ -2211,9 +2211,9 @@ var AuthorityResolver = class _AuthorityResolver {
2211
2211
  * `mobileMeta` 는 `state.leader === 'mobile'` 일 때만 채움 — `'handed_off'` placeholder 의
2212
2212
  * heartbeat / "cols×rows" 표시 데이터.
2213
2213
  */
2214
- _broadcastViewerMode(machineId, mode, meta) {
2214
+ _broadcastViewerMode(machineId, mode2, meta) {
2215
2215
  if (!this._a2SpikeEnabled) return;
2216
- if (mode === "handed_off") {
2216
+ if (mode2 === "handed_off") {
2217
2217
  this._ensureHeartbeatTicker(machineId);
2218
2218
  } else {
2219
2219
  this._stopHeartbeatTicker(machineId);
@@ -2226,13 +2226,13 @@ var AuthorityResolver = class _AuthorityResolver {
2226
2226
  lastActivityAt: s.lastActivityAt
2227
2227
  } : void 0;
2228
2228
  log3.info(
2229
- `broadcast viewer_mode machine=${machineId} mode=${mode}` + (mobileMeta ? ` lastActivityAt=${mobileMeta.lastActivityAt} cols=${mobileMeta.cols} rows=${mobileMeta.rows}` : "") + (meta?.graceUntil ? ` graceUntil=${meta.graceUntil}` : "") + (meta?.cooldownUntil ? ` cooldownUntil=${meta.cooldownUntil}` : "") + (meta?.reason ? ` reason=${meta.reason}` : "")
2229
+ `broadcast viewer_mode machine=${machineId} mode=${mode2}` + (mobileMeta ? ` lastActivityAt=${mobileMeta.lastActivityAt} cols=${mobileMeta.cols} rows=${mobileMeta.rows}` : "") + (meta?.graceUntil ? ` graceUntil=${meta.graceUntil}` : "") + (meta?.cooldownUntil ? ` cooldownUntil=${meta.cooldownUntil}` : "") + (meta?.reason ? ` reason=${meta.reason}` : "")
2230
2230
  );
2231
2231
  this.deps.broadcast(
2232
2232
  {
2233
2233
  type: "desktop.viewer_mode",
2234
2234
  machineId,
2235
- mode,
2235
+ mode: mode2,
2236
2236
  mobileMeta,
2237
2237
  graceUntil: meta?.graceUntil,
2238
2238
  cooldownUntil: meta?.cooldownUntil,
@@ -6946,10 +6946,19 @@ function loadDotEnv(files = defaultEnvFiles(), allowed = ALLOWED_ENV_KEYS) {
6946
6946
  return loaded;
6947
6947
  }
6948
6948
 
6949
+ // src/entry-mode.ts
6950
+ function resolveEntryMode(i) {
6951
+ if (!i.isTTY) {
6952
+ if (i.isServiceProcess) return "daemon";
6953
+ return i.isServiceActive() ? "serviceBusy" : "daemon";
6954
+ }
6955
+ return i.isServiceActive() ? "manager" : "tui";
6956
+ }
6957
+
6949
6958
  // package.json
6950
6959
  var package_default = {
6951
6960
  name: "@junyoung-kim/reins",
6952
- version: "0.1.0",
6961
+ version: "0.1.1",
6953
6962
  type: "module",
6954
6963
  description: "\uD3F0\uC5D0\uC11C AI \uCF54\uB529 \uC5D0\uC774\uC804\uD2B8\uC758 \uACE0\uC090\uB97C \uC950\uB2E4 \u2014 \uD5E4\uB4DC\uB9AC\uC2A4 TUI (npx @junyoung-kim/reins)",
6955
6964
  bin: {
@@ -7075,6 +7084,16 @@ import fs9 from "fs";
7075
7084
  // src/service/unit-template.ts
7076
7085
  import os9 from "os";
7077
7086
  import path11 from "path";
7087
+
7088
+ // src/service/service-process.ts
7089
+ var SERVICE_ENV_KEY = "REINS_RUN_AS_SERVICE";
7090
+ var SERVICE_ENV_VALUE = "1";
7091
+ var SERVICE_ENV_MARKER = `${SERVICE_ENV_KEY}=${SERVICE_ENV_VALUE}`;
7092
+ function isServiceProcess(env) {
7093
+ return env[SERVICE_ENV_KEY] === SERVICE_ENV_VALUE || !!env.INVOCATION_ID;
7094
+ }
7095
+
7096
+ // src/service/unit-template.ts
7078
7097
  var SERVICE_NAME = "reins";
7079
7098
  var UNIT_FILE_NAME = `${SERVICE_NAME}.service`;
7080
7099
  function renderUnit(p2) {
@@ -7087,6 +7106,7 @@ function renderUnit(p2) {
7087
7106
  "",
7088
7107
  "[Service]",
7089
7108
  "Type=simple",
7109
+ `Environment=${SERVICE_ENV_MARKER}`,
7090
7110
  `ExecStart=${exec2}`,
7091
7111
  "Restart=always",
7092
7112
  "RestartSec=3",
@@ -7353,8 +7373,8 @@ var HeadlessStore = class extends EventEmitter {
7353
7373
  };
7354
7374
 
7355
7375
  // src/ui/App.tsx
7356
- import { Box as Box9, Text as Text9, useApp } from "ink";
7357
- import { useEffect as useEffect4, useState as useState7 } from "react";
7376
+ import { Box as Box9, Text as Text10, useApp } from "ink";
7377
+ import { useEffect as useEffect5, useState as useState8 } from "react";
7358
7378
 
7359
7379
  // src/ui/screens/InfoScreen.tsx
7360
7380
  import os11 from "os";
@@ -7919,7 +7939,7 @@ function MachinesScreen({
7919
7939
  onBack
7920
7940
  }) {
7921
7941
  const [selected, setSelected] = useState4(0);
7922
- const [mode, setMode] = useState4("list");
7942
+ const [mode2, setMode] = useState4("list");
7923
7943
  const [editing, setEditing] = useState4(null);
7924
7944
  const count = machines.length;
7925
7945
  const sel = Math.min(selected, Math.max(0, count - 1));
@@ -7943,15 +7963,15 @@ function MachinesScreen({
7943
7963
  if (machines[sel]) setMode("confirm");
7944
7964
  }
7945
7965
  },
7946
- { isActive: active && mode === "list" }
7966
+ { isActive: active && mode2 === "list" }
7947
7967
  );
7948
7968
  useInput3(
7949
7969
  (_input, key) => {
7950
7970
  if (key.escape) setMode("list");
7951
7971
  },
7952
- { isActive: active && mode === "confirm" }
7972
+ { isActive: active && mode2 === "confirm" }
7953
7973
  );
7954
- if (mode === "form") {
7974
+ if (mode2 === "form") {
7955
7975
  return /* @__PURE__ */ jsx6(
7956
7976
  MachineFormScreen,
7957
7977
  {
@@ -7989,13 +8009,13 @@ function MachinesScreen({
7989
8009
  " more"
7990
8010
  ] }) : null,
7991
8011
  /* @__PURE__ */ jsx6(Text6, { color: "gray", children: DIVIDER }),
7992
- mode === "confirm" && target ? /* @__PURE__ */ jsx6(
8012
+ mode2 === "confirm" && target ? /* @__PURE__ */ jsx6(
7993
8013
  ConfirmDialog,
7994
8014
  {
7995
8015
  message: `Delete "${target.name}"?`,
7996
8016
  confirmLabel: "Delete",
7997
8017
  warning: deleteWarning,
7998
- isActive: active && mode === "confirm",
8018
+ isActive: active && mode2 === "confirm",
7999
8019
  onConfirm: () => {
8000
8020
  try {
8001
8021
  core.ssh.removeMachine(target.id);
@@ -8012,8 +8032,55 @@ function MachinesScreen({
8012
8032
  }
8013
8033
 
8014
8034
  // src/ui/screens/MainScreen.tsx
8015
- import { Box as Box7, Text as Text7, useInput as useInput4 } from "ink";
8016
- import { useEffect as useEffect3, useMemo as useMemo2, useState as useState5 } from "react";
8035
+ import { Box as Box7, Text as Text8, useInput as useInput4 } from "ink";
8036
+ import { useEffect as useEffect4, useMemo as useMemo2, useState as useState6 } from "react";
8037
+
8038
+ // src/ui/components/PairQrCell.tsx
8039
+ import { Text as Text7 } from "ink";
8040
+
8041
+ // src/ui/qr-fit.ts
8042
+ var RESERVED_ROWS = 10;
8043
+ function qrDimensions(qrText) {
8044
+ const lines = qrText.split("\n");
8045
+ let width = 0;
8046
+ for (const line of lines) {
8047
+ const w2 = [...line].length;
8048
+ if (w2 > width) width = w2;
8049
+ }
8050
+ return { width, height: lines.length };
8051
+ }
8052
+ function qrLayout(qrText, columns, rows, reserveRows = RESERVED_ROWS) {
8053
+ const { width, height } = qrDimensions(qrText);
8054
+ return { width, height, fits: width <= columns && height <= rows - reserveRows };
8055
+ }
8056
+
8057
+ // src/ui/components/PairQrCell.tsx
8058
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8059
+ function PairQrCell({
8060
+ qrText,
8061
+ visible,
8062
+ columns,
8063
+ rows,
8064
+ reserveRows
8065
+ }) {
8066
+ if (!qrText) return null;
8067
+ const { width, height, fits } = qrLayout(qrText, columns, rows, reserveRows);
8068
+ if (!visible) {
8069
+ return /* @__PURE__ */ jsx7(Text7, { color: "gray", children: fits ? "(press c to show QR)" : "(terminal too small for QR \u2014 use URL)" });
8070
+ }
8071
+ if (fits) return /* @__PURE__ */ jsx7(Text7, { children: qrText });
8072
+ return /* @__PURE__ */ jsxs7(Text7, { color: "yellow", children: [
8073
+ "\u26A0 QR needs ",
8074
+ width,
8075
+ "x",
8076
+ height,
8077
+ ", terminal is ",
8078
+ columns,
8079
+ "x",
8080
+ rows,
8081
+ " \u2014 widen the window or use the URL below."
8082
+ ] });
8083
+ }
8017
8084
 
8018
8085
  // src/ui/qr.ts
8019
8086
  import qrcode from "qrcode-terminal";
@@ -8034,8 +8101,31 @@ function scrollWindow(total, scroll, rows) {
8034
8101
  return { start, end, hiddenAbove: start, hiddenBelow: total - end, maxScroll };
8035
8102
  }
8036
8103
 
8104
+ // src/ui/useTerminalSize.ts
8105
+ import { useStdout } from "ink";
8106
+ import { useEffect as useEffect3, useState as useState5 } from "react";
8107
+ var FALLBACK = { columns: 80, rows: 24 };
8108
+ function useTerminalSize() {
8109
+ const { stdout } = useStdout();
8110
+ const [size, setSize] = useState5(() => ({
8111
+ columns: stdout?.columns ?? FALLBACK.columns,
8112
+ rows: stdout?.rows ?? FALLBACK.rows
8113
+ }));
8114
+ useEffect3(() => {
8115
+ if (!stdout) return;
8116
+ const onResize = () => {
8117
+ setSize({ columns: stdout.columns ?? FALLBACK.columns, rows: stdout.rows ?? FALLBACK.rows });
8118
+ };
8119
+ stdout.on("resize", onResize);
8120
+ return () => {
8121
+ stdout.off("resize", onResize);
8122
+ };
8123
+ }, [stdout]);
8124
+ return size;
8125
+ }
8126
+
8037
8127
  // src/ui/screens/MainScreen.tsx
8038
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8128
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
8039
8129
  var VERBOSE_ROWS = 16;
8040
8130
  var PTY_COLOR = {
8041
8131
  idle: "gray",
@@ -8049,45 +8139,46 @@ function DefaultBody({
8049
8139
  qrText,
8050
8140
  qrUrl,
8051
8141
  qrMode,
8052
- qrVisible
8142
+ qrVisible,
8143
+ columns,
8144
+ rows
8053
8145
  }) {
8054
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
8055
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: DIVIDER }),
8056
- qrUrl ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
8057
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Scan to pair:" }),
8058
- qrVisible && qrText ? /* @__PURE__ */ jsx7(Text7, { children: qrText }) : null,
8059
- /* @__PURE__ */ jsxs7(Box7, { children: [
8060
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "URL " }),
8061
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: qrUrl }),
8062
- qrMode ? /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
8146
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
8147
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: DIVIDER }),
8148
+ qrUrl ? /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
8149
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "Scan to pair:" }),
8150
+ /* @__PURE__ */ jsx8(PairQrCell, { qrText, visible: qrVisible, columns, rows }),
8151
+ /* @__PURE__ */ jsxs8(Box7, { children: [
8152
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "URL " }),
8153
+ /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: qrUrl }),
8154
+ qrMode ? /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
8063
8155
  " (",
8064
8156
  qrMode,
8065
8157
  ")"
8066
8158
  ] }) : null
8067
- ] }),
8068
- !qrVisible ? /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "(press c to show QR)" }) : null
8069
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "Preparing QR\u2026 (waiting for relay)" }),
8070
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: DIVIDER }),
8071
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Sessions" }),
8072
- sessions.map((sess) => /* @__PURE__ */ jsxs7(Box7, { children: [
8073
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " " }),
8074
- /* @__PURE__ */ jsx7(Text7, { children: sess.name }),
8075
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " \xB7 " }),
8076
- /* @__PURE__ */ jsx7(Text7, { color: PTY_COLOR[sess.pty], children: sess.pty })
8159
+ ] })
8160
+ ] }) : /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "Preparing QR\u2026 (waiting for relay)" }),
8161
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: DIVIDER }),
8162
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "Sessions" }),
8163
+ sessions.map((sess) => /* @__PURE__ */ jsxs8(Box7, { children: [
8164
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " " }),
8165
+ /* @__PURE__ */ jsx8(Text8, { children: sess.name }),
8166
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " \xB7 " }),
8167
+ /* @__PURE__ */ jsx8(Text8, { color: PTY_COLOR[sess.pty], children: sess.pty })
8077
8168
  ] }, sess.id)),
8078
- /* @__PURE__ */ jsxs7(Box7, { children: [
8079
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Clients " }),
8080
- /* @__PURE__ */ jsx7(Text7, { color: clients > 0 ? "green" : "gray", children: clients })
8169
+ /* @__PURE__ */ jsxs8(Box7, { children: [
8170
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "Clients " }),
8171
+ /* @__PURE__ */ jsx8(Text8, { color: clients > 0 ? "green" : "gray", children: clients })
8081
8172
  ] })
8082
8173
  ] });
8083
8174
  }
8084
8175
  function VerboseLog({ logs, scroll }) {
8085
8176
  const { start, end, hiddenAbove, hiddenBelow } = scrollWindow(logs.length, scroll, VERBOSE_ROWS);
8086
8177
  const view = logs.slice(start, end);
8087
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
8088
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: hiddenAbove > 0 ? `\u2191 ${hiddenAbove} more` : DIVIDER }),
8089
- view.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "\u2026" }) : view.map((line, i) => /* @__PURE__ */ jsx7(Text7, { color: "gray", children: line }, `${start + i}-${line}`)),
8090
- hiddenBelow > 0 ? /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
8178
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
8179
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: hiddenAbove > 0 ? `\u2191 ${hiddenAbove} more` : DIVIDER }),
8180
+ view.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u2026" }) : view.map((line, i) => /* @__PURE__ */ jsx8(Text8, { color: "gray", children: line }, `${start + i}-${line}`)),
8181
+ hiddenBelow > 0 ? /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
8091
8182
  "\u2193 ",
8092
8183
  hiddenBelow,
8093
8184
  " more"
@@ -8101,13 +8192,14 @@ function MainScreen({
8101
8192
  onNavigate,
8102
8193
  onExit
8103
8194
  }) {
8104
- const [verbose, setVerbose] = useState5(false);
8105
- const [scroll, setScroll] = useState5(0);
8106
- const [clients, setClients] = useState5(0);
8107
- const [qrVisible, setQrVisible] = useState5(false);
8108
- const [handoffOnQuit, setHandoffOnQuit] = useState5(false);
8109
- const [autostartNotice, setAutostartNotice] = useState5(null);
8110
- useEffect3(() => {
8195
+ const { columns, rows } = useTerminalSize();
8196
+ const [verbose, setVerbose] = useState6(false);
8197
+ const [scroll, setScroll] = useState6(0);
8198
+ const [clients, setClients] = useState6(0);
8199
+ const [qrVisible, setQrVisible] = useState6(false);
8200
+ const [handoffOnQuit, setHandoffOnQuit] = useState6(false);
8201
+ const [autostartNotice, setAutostartNotice] = useState6(null);
8202
+ useEffect4(() => {
8111
8203
  const t = setInterval(() => {
8112
8204
  setClients(core.ws.getActiveMobileClientCount());
8113
8205
  }, 1e3);
@@ -8177,8 +8269,8 @@ function MainScreen({
8177
8269
  pty: state.sessions[m2.id] ?? "idle"
8178
8270
  }));
8179
8271
  const qrText = useMemo2(() => state.qr ? renderQr(state.qr.url) : null, [state.qr?.url]);
8180
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
8181
- verbose ? /* @__PURE__ */ jsx7(VerboseLog, { logs: state.logs, scroll }) : /* @__PURE__ */ jsx7(
8272
+ return /* @__PURE__ */ jsxs8(Box7, { flexDirection: "column", children: [
8273
+ verbose ? /* @__PURE__ */ jsx8(VerboseLog, { logs: state.logs, scroll }) : /* @__PURE__ */ jsx8(
8182
8274
  DefaultBody,
8183
8275
  {
8184
8276
  sessions,
@@ -8186,12 +8278,14 @@ function MainScreen({
8186
8278
  qrText,
8187
8279
  qrUrl: state.qr ? prettyUrl(state.qr.url) : null,
8188
8280
  qrMode: state.qr?.mode ?? null,
8189
- qrVisible
8281
+ qrVisible,
8282
+ columns,
8283
+ rows
8190
8284
  }
8191
8285
  ),
8192
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: DIVIDER }),
8193
- autostartNotice ? /* @__PURE__ */ jsx7(Text7, { color: "yellow", children: autostartNotice }) : null,
8194
- /* @__PURE__ */ jsxs7(Text7, { color: "cyan", children: [
8286
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: DIVIDER }),
8287
+ autostartNotice ? /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: autostartNotice }) : null,
8288
+ /* @__PURE__ */ jsxs8(Text8, { color: "cyan", children: [
8195
8289
  "q quit \xB7 r reconnect \xB7 n new QR \xB7 m machines \xB7 o options \xB7 i info \xB7 a auto-start \xB7 c",
8196
8290
  " ",
8197
8291
  qrVisible ? "hide" : "show",
@@ -8203,9 +8297,9 @@ function MainScreen({
8203
8297
  }
8204
8298
 
8205
8299
  // src/ui/screens/OptionsScreen.tsx
8206
- import { Box as Box8, Text as Text8 } from "ink";
8207
- import { useState as useState6 } from "react";
8208
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
8300
+ import { Box as Box8, Text as Text9 } from "ink";
8301
+ import { useState as useState7 } from "react";
8302
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
8209
8303
  var ORDER = ["relayUrls", "wsPort", "heartbeat", "pong", "auth"];
8210
8304
  function loadForm(config2) {
8211
8305
  return {
@@ -8222,9 +8316,9 @@ function OptionsScreen({
8222
8316
  active,
8223
8317
  onBack
8224
8318
  }) {
8225
- const [form, setForm] = useState6(() => loadForm(config2));
8226
- const [errors, setErrors] = useState6([]);
8227
- const [saved, setSaved] = useState6(false);
8319
+ const [form, setForm] = useState7(() => loadForm(config2));
8320
+ const [errors, setErrors] = useState7([]);
8321
+ const [saved, setSaved] = useState7(false);
8228
8322
  const set = (patch) => {
8229
8323
  setForm((f) => ({ ...f, ...patch }));
8230
8324
  setSaved(false);
@@ -8268,10 +8362,10 @@ function OptionsScreen({
8268
8362
  onEscape: onBack
8269
8363
  });
8270
8364
  const invalid = (k2) => errors.includes(k2);
8271
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
8272
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: DIVIDER }),
8273
- /* @__PURE__ */ jsx8(Text8, { children: "Options" }),
8274
- /* @__PURE__ */ jsx8(
8365
+ return /* @__PURE__ */ jsxs9(Box8, { flexDirection: "column", children: [
8366
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: DIVIDER }),
8367
+ /* @__PURE__ */ jsx9(Text9, { children: "Options" }),
8368
+ /* @__PURE__ */ jsx9(
8275
8369
  Field,
8276
8370
  {
8277
8371
  label: "Relay",
@@ -8283,7 +8377,7 @@ function OptionsScreen({
8283
8377
  invalid: invalid("relayUrls")
8284
8378
  }
8285
8379
  ),
8286
- /* @__PURE__ */ jsx8(
8380
+ /* @__PURE__ */ jsx9(
8287
8381
  Field,
8288
8382
  {
8289
8383
  label: "WS Port",
@@ -8294,7 +8388,7 @@ function OptionsScreen({
8294
8388
  invalid: invalid("wsPort")
8295
8389
  }
8296
8390
  ),
8297
- /* @__PURE__ */ jsx8(
8391
+ /* @__PURE__ */ jsx9(
8298
8392
  Field,
8299
8393
  {
8300
8394
  label: "Heartbeat",
@@ -8305,7 +8399,7 @@ function OptionsScreen({
8305
8399
  invalid: invalid("heartbeat")
8306
8400
  }
8307
8401
  ),
8308
- /* @__PURE__ */ jsx8(
8402
+ /* @__PURE__ */ jsx9(
8309
8403
  Field,
8310
8404
  {
8311
8405
  label: "Pong",
@@ -8316,7 +8410,7 @@ function OptionsScreen({
8316
8410
  invalid: invalid("pong")
8317
8411
  }
8318
8412
  ),
8319
- /* @__PURE__ */ jsx8(
8413
+ /* @__PURE__ */ jsx9(
8320
8414
  Field,
8321
8415
  {
8322
8416
  label: "Auth",
@@ -8327,29 +8421,29 @@ function OptionsScreen({
8327
8421
  invalid: invalid("auth")
8328
8422
  }
8329
8423
  ),
8330
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "\u26A0 WS Port\xB7timeouts apply after restart (relay reconnects immediately)." }),
8331
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: DIVIDER }),
8332
- /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
8424
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "\u26A0 WS Port\xB7timeouts apply after restart (relay reconnects immediately)." }),
8425
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: DIVIDER }),
8426
+ /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
8333
8427
  "install_id ",
8334
8428
  config2.getInstallId().slice(0, 8),
8335
8429
  "\u2026"
8336
8430
  ] }),
8337
- /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
8431
+ /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
8338
8432
  "FCM tokens ",
8339
8433
  config2.getFcmTokens().length
8340
8434
  ] }),
8341
- /* @__PURE__ */ jsx8(Text8, { color: "gray", children: DIVIDER }),
8342
- errors.length > 0 ? /* @__PURE__ */ jsx8(Text8, { color: "red", children: errors.includes("save") ? "Save failed \u2014 please retry" : `Format error: ${errors.join(", ")}` }) : null,
8343
- saved ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: "Saved (port/timeouts apply after restart)" }) : null,
8344
- /* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "Tab move \xB7 Enter next/save \xB7 Esc back" })
8435
+ /* @__PURE__ */ jsx9(Text9, { color: "gray", children: DIVIDER }),
8436
+ errors.length > 0 ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: errors.includes("save") ? "Save failed \u2014 please retry" : `Format error: ${errors.join(", ")}` }) : null,
8437
+ saved ? /* @__PURE__ */ jsx9(Text9, { color: "green", children: "Saved (port/timeouts apply after restart)" }) : null,
8438
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Tab move \xB7 Enter next/save \xB7 Esc back" })
8345
8439
  ] });
8346
8440
  }
8347
8441
 
8348
8442
  // src/ui/App.tsx
8349
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
8443
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
8350
8444
  function useStore(store) {
8351
- const [state, setState] = useState7(store.getState());
8352
- useEffect4(() => {
8445
+ const [state, setState] = useState8(store.getState());
8446
+ useEffect5(() => {
8353
8447
  const onChange = () => setState({ ...store.getState() });
8354
8448
  store.on("change", onChange);
8355
8449
  return () => {
@@ -8371,37 +8465,37 @@ function App({
8371
8465
  }) {
8372
8466
  const state = useStore(store);
8373
8467
  const { exit } = useApp();
8374
- const [screen, setScreen] = useState7("main");
8375
- useEffect4(() => {
8468
+ const [screen, setScreen] = useState8("main");
8469
+ useEffect5(() => {
8376
8470
  if (state.relayStatus === "connected") core.ws.requestQr();
8377
8471
  }, [state.relayStatus, core]);
8378
8472
  const s = STATUS[state.relayStatus] ?? { dot: "\xB7", color: "gray" };
8379
8473
  const retry = state.relayRetry ? ` (retry ${state.relayRetry})` : "";
8380
8474
  const relayTarget = config2.getRelayUrls().join(", ");
8381
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [
8382
- /* @__PURE__ */ jsxs9(Box9, { justifyContent: "space-between", children: [
8383
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "reins" }),
8384
- /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
8475
+ return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [
8476
+ /* @__PURE__ */ jsxs10(Box9, { justifyContent: "space-between", children: [
8477
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "reins" }),
8478
+ /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
8385
8479
  "v",
8386
8480
  identity.version
8387
8481
  ] })
8388
8482
  ] }),
8389
- /* @__PURE__ */ jsxs9(Box9, { children: [
8390
- /* @__PURE__ */ jsx9(Text9, { color: "gray", children: "Relay " }),
8391
- /* @__PURE__ */ jsxs9(Text9, { color: s.color, children: [
8483
+ /* @__PURE__ */ jsxs10(Box9, { children: [
8484
+ /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Relay " }),
8485
+ /* @__PURE__ */ jsxs10(Text10, { color: s.color, children: [
8392
8486
  s.dot,
8393
8487
  " ",
8394
8488
  state.relayStatus,
8395
8489
  retry
8396
8490
  ] }),
8397
- state.qr ? /* @__PURE__ */ jsxs9(Text9, { color: "gray", children: [
8491
+ state.qr ? /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
8398
8492
  " (",
8399
8493
  state.qr.mode,
8400
8494
  ")"
8401
8495
  ] }) : null
8402
8496
  ] }),
8403
- /* @__PURE__ */ jsx9(Text9, { color: "gray", children: relayTarget }),
8404
- screen === "main" ? /* @__PURE__ */ jsx9(
8497
+ /* @__PURE__ */ jsx10(Text10, { color: "gray", children: relayTarget }),
8498
+ screen === "main" ? /* @__PURE__ */ jsx10(
8405
8499
  MainScreen,
8406
8500
  {
8407
8501
  state,
@@ -8411,7 +8505,7 @@ function App({
8411
8505
  onExit: exit
8412
8506
  }
8413
8507
  ) : null,
8414
- screen === "machines" ? /* @__PURE__ */ jsx9(
8508
+ screen === "machines" ? /* @__PURE__ */ jsx10(
8415
8509
  MachinesScreen,
8416
8510
  {
8417
8511
  machines: core.ssh.getMachines(),
@@ -8420,8 +8514,8 @@ function App({
8420
8514
  onBack: () => setScreen("main")
8421
8515
  }
8422
8516
  ) : null,
8423
- screen === "options" ? /* @__PURE__ */ jsx9(OptionsScreen, { config: config2, core, active: true, onBack: () => setScreen("main") }) : null,
8424
- screen === "info" ? /* @__PURE__ */ jsx9(
8517
+ screen === "options" ? /* @__PURE__ */ jsx10(OptionsScreen, { config: config2, core, active: true, onBack: () => setScreen("main") }) : null,
8518
+ screen === "info" ? /* @__PURE__ */ jsx10(
8425
8519
  InfoScreen,
8426
8520
  {
8427
8521
  state,
@@ -8435,8 +8529,8 @@ function App({
8435
8529
  }
8436
8530
 
8437
8531
  // src/ui/ManagerApp.tsx
8438
- import { Box as Box10, Text as Text10, useApp as useApp2, useInput as useInput5 } from "ink";
8439
- import { useEffect as useEffect5, useMemo as useMemo3, useState as useState8 } from "react";
8532
+ import { Box as Box10, Text as Text11, useApp as useApp2, useInput as useInput5 } from "ink";
8533
+ import { useEffect as useEffect6, useMemo as useMemo3, useState as useState9 } from "react";
8440
8534
 
8441
8535
  // src/ui/log-tail.ts
8442
8536
  import fs11 from "fs";
@@ -8462,15 +8556,17 @@ function tailLog(component, lines) {
8462
8556
  }
8463
8557
 
8464
8558
  // src/ui/ManagerApp.tsx
8465
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
8559
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
8466
8560
  var LOG_ROWS = 8;
8561
+ var QR_RESERVE_ROWS = 10 + LOG_ROWS;
8467
8562
  function ManagerApp({ config: config2 }) {
8468
8563
  const { exit } = useApp2();
8469
- const [status, setStatus] = useState8(() => serviceStatus());
8470
- const [logs, setLogs] = useState8(() => tailLog("ws", LOG_ROWS));
8471
- const [qrVisible, setQrVisible] = useState8(false);
8472
- const [notice, setNotice] = useState8(null);
8473
- useEffect5(() => {
8564
+ const { columns, rows } = useTerminalSize();
8565
+ const [status, setStatus] = useState9(() => serviceStatus());
8566
+ const [logs, setLogs] = useState9(() => tailLog("ws", LOG_ROWS));
8567
+ const [qrVisible, setQrVisible] = useState9(false);
8568
+ const [notice, setNotice] = useState9(null);
8569
+ useEffect6(() => {
8474
8570
  const t = setInterval(() => {
8475
8571
  setStatus(serviceStatus());
8476
8572
  setLogs(tailLog("ws", LOG_ROWS));
@@ -8495,43 +8591,51 @@ function ManagerApp({ config: config2 }) {
8495
8591
  setStatus(serviceStatus());
8496
8592
  }
8497
8593
  });
8498
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [
8499
- /* @__PURE__ */ jsxs10(Box10, { justifyContent: "space-between", children: [
8500
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: "reins \u2014 running as service" }),
8501
- /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
8594
+ return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [
8595
+ /* @__PURE__ */ jsxs11(Box10, { justifyContent: "space-between", children: [
8596
+ /* @__PURE__ */ jsx11(Text11, { bold: true, children: "reins \u2014 running as service" }),
8597
+ /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
8502
8598
  "v",
8503
8599
  identity.version
8504
8600
  ] })
8505
8601
  ] }),
8506
- /* @__PURE__ */ jsxs10(Box10, { children: [
8507
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Service " }),
8508
- /* @__PURE__ */ jsx10(Text10, { color: status.active ? "green" : "red", children: status.active ? "\u25CF active" : "\u25CB inactive" }),
8509
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: status.enabled ? " (enabled)" : " (disabled)" })
8602
+ /* @__PURE__ */ jsxs11(Box10, { children: [
8603
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Service " }),
8604
+ /* @__PURE__ */ jsx11(Text11, { color: status.active ? "green" : "red", children: status.active ? "\u25CF active" : "\u25CB inactive" }),
8605
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: status.enabled ? " (enabled)" : " (disabled)" })
8510
8606
  ] }),
8511
- /* @__PURE__ */ jsxs10(Box10, { children: [
8512
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Linger " }),
8513
- status.linger ? /* @__PURE__ */ jsx10(Text10, { color: "green", children: "on \u2192 survives SSH logout & reboot" }) : /* @__PURE__ */ jsxs10(Text10, { color: "yellow", children: [
8607
+ /* @__PURE__ */ jsxs11(Box10, { children: [
8608
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Linger " }),
8609
+ status.linger ? /* @__PURE__ */ jsx11(Text11, { color: "green", children: "on \u2192 survives SSH logout & reboot" }) : /* @__PURE__ */ jsxs11(Text11, { color: "yellow", children: [
8514
8610
  "off \u2192 run: sudo loginctl enable-linger ",
8515
8611
  status.user
8516
8612
  ] })
8517
8613
  ] }),
8518
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: DIVIDER }),
8519
- pairUrl ? /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
8520
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Scan to pair:" }),
8521
- qrVisible && qrText ? /* @__PURE__ */ jsx10(Text10, { children: qrText }) : null,
8522
- /* @__PURE__ */ jsxs10(Box10, { children: [
8523
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "URL " }),
8524
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: prettyUrl(pairUrl) })
8614
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: DIVIDER }),
8615
+ pairUrl ? /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", children: [
8616
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Scan to pair:" }),
8617
+ /* @__PURE__ */ jsx11(
8618
+ PairQrCell,
8619
+ {
8620
+ qrText,
8621
+ visible: qrVisible,
8622
+ columns,
8623
+ rows,
8624
+ reserveRows: QR_RESERVE_ROWS
8625
+ }
8626
+ ),
8627
+ /* @__PURE__ */ jsxs11(Box10, { children: [
8628
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "URL " }),
8629
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: prettyUrl(pairUrl) })
8525
8630
  ] }),
8526
- multiRelay ? /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "(assumes primary relay \u2014 may differ if failed over)" }) : null,
8527
- !qrVisible ? /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "(press c to show QR)" }) : null
8528
- ] }) : /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "Pair URL unavailable (session-token / relay URL \uD655\uC778)" }),
8529
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: DIVIDER }),
8530
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Logs (ws.log tail)" }),
8531
- logs.length === 0 ? /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "\u2026" }) : logs.map((line, i) => /* @__PURE__ */ jsx10(Text10, { color: "gray", children: line }, `${i}-${line}`)),
8532
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: DIVIDER }),
8533
- notice ? /* @__PURE__ */ jsx10(Text10, { color: "green", children: notice }) : null,
8534
- /* @__PURE__ */ jsxs10(Text10, { color: "cyan", children: [
8631
+ multiRelay ? /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "(assumes primary relay \u2014 may differ if failed over)" }) : null
8632
+ ] }) : /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "Pair URL unavailable (session-token / relay URL \uD655\uC778)" }),
8633
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: DIVIDER }),
8634
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Logs (ws.log tail)" }),
8635
+ logs.length === 0 ? /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "\u2026" }) : logs.map((line, i) => /* @__PURE__ */ jsx11(Text11, { color: "gray", children: line }, `${i}-${line}`)),
8636
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: DIVIDER }),
8637
+ notice ? /* @__PURE__ */ jsx11(Text11, { color: "green", children: notice }) : null,
8638
+ /* @__PURE__ */ jsxs11(Text11, { color: "cyan", children: [
8535
8639
  "r restart \xB7 x stop \xB7 u disable \xB7 c ",
8536
8640
  qrVisible ? "hide" : "show",
8537
8641
  " qr \xB7 q quit"
@@ -8540,24 +8644,14 @@ function ManagerApp({ config: config2 }) {
8540
8644
  }
8541
8645
 
8542
8646
  // src/cli.tsx
8543
- import { jsx as jsx11 } from "react/jsx-runtime";
8647
+ import { jsx as jsx12 } from "react/jsx-runtime";
8544
8648
  var loadedEnvFiles = loadDotEnv();
8545
- setConsoleLogging(false);
8546
8649
  var config = new HeadlessConfigStore();
8547
8650
  var args = process.argv.slice(2);
8548
8651
  if (args[0] === "service") {
8549
8652
  process.exit(runServiceCommand(args[1], config));
8550
8653
  }
8551
- if (process.stdout.isTTY) {
8552
- process.stdout.write("\x1B[?1049h");
8553
- process.on("exit", () => process.stdout.write("\x1B[?1049l"));
8554
- }
8555
- if (isServiceActive()) {
8556
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(ManagerApp, { config }));
8557
- process.on("SIGTERM", () => process.exit(0));
8558
- void waitUntilExit().finally(() => process.exit(0));
8559
- } else {
8560
- const store = new HeadlessStore();
8654
+ function buildCore(store) {
8561
8655
  const logDir = path16.join(os14.homedir(), ".ai_remote_vibe_agent", "logs");
8562
8656
  const relayTarget = config.getRelayUrls().join(", ");
8563
8657
  for (const f of loadedEnvFiles) store.log(`[boot] .env loaded: ${f}`);
@@ -8571,13 +8665,50 @@ if (isServiceActive()) {
8571
8665
  });
8572
8666
  store.apply({ type: "machine_list", machines: core.ssh.getMachines() });
8573
8667
  core.ws.requestQr();
8574
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, { store, core, config }));
8575
- const finish = (code) => {
8668
+ return core;
8669
+ }
8670
+ var mode = resolveEntryMode({
8671
+ isTTY: !!process.stdout.isTTY,
8672
+ isServiceProcess: isServiceProcess(process.env),
8673
+ isServiceActive
8674
+ });
8675
+ if (mode === "daemon") {
8676
+ runDaemon();
8677
+ } else if (mode === "serviceBusy") {
8678
+ console.error("reins: \uC11C\uBE44\uC2A4\uAC00 \uC774\uBBF8 \uAC00\uB3D9 \uC911\uC774\uB77C \uB450 \uBC88\uC9F8 \uC778\uC2A4\uD134\uC2A4\uB97C \uC2DC\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uC774\uC911 relay\xB7\uD3EC\uD2B8 \uCDA9\uB3CC \uBC29\uC9C0).");
8679
+ console.error(" \uC7AC\uC811\uC18D\uD558\uB824\uBA74 TTY \uC5D0\uC11C `reins` \uB97C, \uB85C\uADF8\uB294 `journalctl --user -u reins -f` \uB97C \uC0AC\uC6A9\uD558\uC138\uC694.");
8680
+ process.exit(0);
8681
+ } else {
8682
+ setConsoleLogging(false);
8683
+ process.stdout.write("\x1B[?1049h");
8684
+ process.on("exit", () => process.stdout.write("\x1B[?1049l"));
8685
+ if (mode === "manager") {
8686
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx12(ManagerApp, { config }));
8687
+ process.on("SIGTERM", () => process.exit(0));
8688
+ void waitUntilExit().finally(() => process.exit(0));
8689
+ } else {
8690
+ const store = new HeadlessStore();
8691
+ const core = buildCore(store);
8692
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx12(App, { store, core, config }));
8693
+ const finish = (code) => {
8694
+ core.cleanup();
8695
+ process.exit(code);
8696
+ };
8697
+ process.on("SIGTERM", () => finish(0));
8698
+ void waitUntilExit().then(() => finish(0)).catch(() => finish(1));
8699
+ }
8700
+ }
8701
+ function runDaemon() {
8702
+ const log10 = createLogger("ws");
8703
+ const store = new HeadlessStore();
8704
+ const core = buildCore(store);
8705
+ log10.info(`[daemon] headless service started \u2014 relay=${config.getRelayUrls().join(", ")}`);
8706
+ const shutdown = () => {
8576
8707
  core.cleanup();
8577
- process.exit(code);
8708
+ process.exit(0);
8578
8709
  };
8579
- process.on("SIGTERM", () => finish(0));
8580
- void waitUntilExit().then(() => finish(0)).catch(() => finish(1));
8710
+ process.on("SIGTERM", shutdown);
8711
+ process.on("SIGINT", shutdown);
8581
8712
  }
8582
8713
  /*! Bundled license information:
8583
8714