@foxden-app/foxclaw 0.2.7 → 0.3.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/README.md CHANGED
@@ -30,6 +30,7 @@ FoxClaw(狸爪)的目标很直接:让你用手机控制本机的 Codex,
30
30
 
31
31
  - 手头有 Codex、OpenClaw、QwenPaw、Hermes、OpenCode、Kimi CLI 之类能跑 shell 的 agent?推荐走 [Agent 辅助安装](./docs/zh/agent-assisted-install.md)。
32
32
  - 对 Node、Telegram 机器人、Codex CLI 不太熟?看 [新手安装指南](./docs/zh/install-for-beginners.md)。
33
+ - 已经装好,想系统了解 `/help`、`/setup`、`/threads`、`/watch`、`/auth` 和账号轮转?看 [用户手册](./docs/zh/user-manual.md)。
33
34
  - Git、Node、`.env` 都玩得转?直接往下看快速设置。
34
35
  - 卡住了?看 [故障排查](./docs/zh/troubleshooting.md)。
35
36
 
@@ -138,6 +139,14 @@ systemctl --user status foxclaw.service
138
139
  journalctl --user -u foxclaw.service -f
139
140
  ```
140
141
 
142
+ 也可以直接用包装命令:
143
+
144
+ ```bash
145
+ foxclaw status
146
+ foxclaw restart
147
+ foxclaw stop
148
+ ```
149
+
141
150
  需要前台调试时:
142
151
 
143
152
  ```bash
@@ -291,6 +300,8 @@ foxclaw weixin-login
291
300
  foxclaw doctor
292
301
  foxclaw status
293
302
  foxclaw start
303
+ foxclaw restart
304
+ foxclaw stop
294
305
  foxclaw uninstall-systemd
295
306
  ```
296
307
 
package/README_EN.md CHANGED
@@ -30,6 +30,7 @@ No public server required. FoxClaw runs on your own computer, talks to `codex ap
30
30
 
31
31
  - Already have a shell-capable agent such as Codex, OpenClaw, QwenPaw, Hermes, OpenCode, or Kimi CLI? Use the [Agent-Assisted Install](./docs/agent-assisted-install.md) first. This is the recommended path.
32
32
  - New to Node, Telegram bots, or Codex CLI? Use the [Beginner Install Guide](./docs/install-for-beginners.md).
33
+ - Already installed and want the full command guide for `/help`, `/setup`, `/threads`, `/watch`, `/auth`, and auth rotation? Read the [User Manual](./docs/user-manual.md).
33
34
  - Already comfortable with Git, Node, and `.env` files? Use the quick setup below.
34
35
  - Something failed? Check [Troubleshooting](./docs/troubleshooting.md).
35
36
 
@@ -138,6 +139,14 @@ systemctl --user status foxclaw.service
138
139
  journalctl --user -u foxclaw.service -f
139
140
  ```
140
141
 
142
+ You can also use the wrapper commands:
143
+
144
+ ```bash
145
+ foxclaw status
146
+ foxclaw restart
147
+ foxclaw stop
148
+ ```
149
+
141
150
  For foreground debugging:
142
151
 
143
152
  ```bash
@@ -291,6 +300,8 @@ See [Troubleshooting](./docs/troubleshooting.md) for `doctor` failures, Telegram
291
300
  foxclaw doctor
292
301
  foxclaw status
293
302
  foxclaw start
303
+ foxclaw restart
304
+ foxclaw stop
294
305
  foxclaw uninstall-systemd
295
306
  ```
296
307
 
@@ -260,6 +260,7 @@ export declare class BridgeSessionCore {
260
260
  private handleThreadOpenCallback;
261
261
  private handleThreadActionCallback;
262
262
  private handleThreadNewCallback;
263
+ private handleThreadNewFromThreadCallback;
263
264
  private handleThreadNewCwdCallback;
264
265
  private handleThreadNewCwdTextReply;
265
266
  private startNewThreadForRequestedCwd;
@@ -893,6 +893,11 @@ export class BridgeSessionCore {
893
893
  await this.handleThreadNewCallback(event, locale);
894
894
  return;
895
895
  }
896
+ const threadNewMatch = /^thread:new:(.+)$/.exec(event.data);
897
+ if (threadNewMatch) {
898
+ await this.handleThreadNewFromThreadCallback(event, threadNewMatch[1], locale);
899
+ return;
900
+ }
896
901
  const newCwdMatch = /^thread:newcwd:(create|cancel)$/.exec(event.data);
897
902
  if (newCwdMatch) {
898
903
  await this.handleThreadNewCwdCallback(event, newCwdMatch[1], locale);
@@ -4683,6 +4688,18 @@ export class BridgeSessionCore {
4683
4688
  await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'thread_new_prompt_short'));
4684
4689
  await this.sendMessage(event.scopeId, t(locale, 'thread_new_prompt', { cwd: this.config.defaultCwd }));
4685
4690
  }
4691
+ async handleThreadNewFromThreadCallback(event, threadId, locale) {
4692
+ const scopeId = event.scopeId;
4693
+ const cached = this.store.listCachedThreads(scopeId).find(thread => thread.threadId === threadId);
4694
+ if (!cached) {
4695
+ await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'cached_thread_unavailable'));
4696
+ return;
4697
+ }
4698
+ this.pendingThreadRenames.delete(scopeId);
4699
+ this.pendingThreadNewCwds.delete(scopeId);
4700
+ await this.messaging.answerCallback(event.callbackQueryId, t(locale, 'thread_new_started_short'));
4701
+ await this.startNewThreadForRequestedCwd(scopeId, locale, cached.cwd || this.config.defaultCwd, event.messageId);
4702
+ }
4686
4703
  async handleThreadNewCwdCallback(event, action, locale) {
4687
4704
  const pending = this.pendingThreadNewCwds.get(event.scopeId);
4688
4705
  if (!pending || !pending.cwdToCreate) {
@@ -6984,7 +7001,7 @@ function authChoiceKeyboard(locale, record) {
6984
7001
  callback_data: `auth:${record.localId}:${index}`,
6985
7002
  },
6986
7003
  {
6987
- text: t(locale, candidate.disabled ? 'button_auth_enable' : 'button_auth_disable'),
7004
+ text: t(locale, candidate.disabled ? 'button_auth_disable' : 'button_auth_enable'),
6988
7005
  callback_data: `auth:${record.localId}:toggle:${index}`,
6989
7006
  },
6990
7007
  ]);
@@ -47,6 +47,7 @@ export function buildThreadsKeyboard(locale, threads) {
47
47
  { text: t(locale, 'button_thread_rename'), callback_data: `thread:rename:${thread.threadId}` },
48
48
  { text: t(locale, 'button_thread_watch'), callback_data: `thread:watch:${thread.threadId}` },
49
49
  { text: t(locale, 'button_thread_archive'), callback_data: `thread:archive:${thread.threadId}` },
50
+ { text: t(locale, 'button_thread_new'), callback_data: `thread:new:${thread.threadId}` },
50
51
  ];
51
52
  return [openRow, actionRow];
52
53
  });
@@ -250,23 +251,24 @@ export function buildSetupPanelKeyboard(locale, ctx) {
250
251
  const currentMode = resolveCollaborationMode(ctx.settings?.collaborationMode ?? null);
251
252
  const activeTurnMessageMode = resolveActiveTurnMessageMode(ctx.settings?.activeTurnMessageMode ?? null);
252
253
  const rows = [];
254
+ const autoLabel = setupAutoButtonLabel(locale);
253
255
  rows.push(...chunkButtons([
254
256
  {
255
- text: currentModel === null ? `• ${t(locale, 'button_auto')}` : t(locale, 'button_auto'),
257
+ text: currentModel === null ? `• ${autoLabel}` : autoLabel,
256
258
  callback_data: 'setup:model:default',
257
259
  },
258
260
  ...ctx.models.map((model) => ({
259
- text: selectedButtonText(currentModel === model.model, `🤖 ${truncate(model.model, 14)}`),
261
+ text: selectedButtonText(currentModel === model.model, truncate(model.model, 14)),
260
262
  callback_data: `setup:model:${encodeURIComponent(model.model)}`,
261
263
  })),
262
264
  ], 2));
263
265
  rows.push(...chunkButtons([
264
266
  {
265
- text: (ctx.settings?.reasoningEffort ?? null) === null ? `• ${t(locale, 'button_auto')}` : t(locale, 'button_auto'),
267
+ text: (ctx.settings?.reasoningEffort ?? null) === null ? `• ${autoLabel}` : autoLabel,
266
268
  callback_data: 'setup:effort:default',
267
269
  },
268
270
  ...efforts.map((effort) => ({
269
- text: selectedButtonText(ctx.settings?.reasoningEffort === effort, `🧠 ${effort}`),
271
+ text: selectedButtonText(ctx.settings?.reasoningEffort === effort, effort),
270
272
  callback_data: `setup:effort:${effort}`,
271
273
  })),
272
274
  ], 3));
@@ -301,7 +303,7 @@ export function buildSetupPanelKeyboard(locale, ctx) {
301
303
  ]);
302
304
  rows.push([
303
305
  {
304
- text: selectedButtonText(currentMode === 'default', `🤖 ${t(locale, 'collaboration_mode_default')}`),
306
+ text: selectedButtonText(currentMode === 'default', t(locale, 'collaboration_mode_default')),
305
307
  callback_data: 'setup:mode:default',
306
308
  },
307
309
  {
@@ -311,11 +313,11 @@ export function buildSetupPanelKeyboard(locale, ctx) {
311
313
  ]);
312
314
  rows.push([
313
315
  {
314
- text: selectedButtonText(activeTurnMessageMode === 'steer', `🎯 ${t(locale, 'active_turn_message_mode_steer')}`),
316
+ text: selectedButtonText(activeTurnMessageMode === 'steer', t(locale, 'active_turn_message_mode_steer')),
315
317
  callback_data: 'setup:active:steer',
316
318
  },
317
319
  {
318
- text: selectedButtonText(activeTurnMessageMode === 'queue', `📥 ${t(locale, 'active_turn_message_mode_queue')}`),
320
+ text: selectedButtonText(activeTurnMessageMode === 'queue', t(locale, 'active_turn_message_mode_queue')),
319
321
  callback_data: 'setup:active:queue',
320
322
  },
321
323
  ]);
@@ -392,6 +394,9 @@ function accessPresetButtonLabel(locale, preset) {
392
394
  return `🔓 ${formatAccessPresetLabel(locale, preset)}`;
393
395
  return `🛡️ ${formatAccessPresetLabel(locale, preset)}`;
394
396
  }
397
+ function setupAutoButtonLabel(locale) {
398
+ return locale === 'zh' ? '自动' : 'Auto';
399
+ }
395
400
  export function formatModelSettingsMessage(locale, models, settings) {
396
401
  const selectedModel = resolveCurrentModel(models, settings?.model ?? null);
397
402
  const selectedModelLabel = settings?.model ?? t(locale, 'server_default');
package/dist/i18n.d.ts CHANGED
@@ -150,8 +150,8 @@ declare const MESSAGES: {
150
150
  readonly auth_add_missing_file: "Login completed, but the new auth file was not created: {value}";
151
151
  readonly button_login_device: "🔑 Login";
152
152
  readonly button_auth_reload: "🔄 Reload auth";
153
- readonly button_auth_enable: "✅ Enable";
154
- readonly button_auth_disable: "⏸️ Disable";
153
+ readonly button_auth_enable: "✅";
154
+ readonly button_auth_disable: "⏸️";
155
155
  readonly another_turn_running: "Another turn is already running. Use /interrupt, /takeover, /queue, or wait.";
156
156
  readonly working: "Working...";
157
157
  readonly usage_open: "Usage: /open <n>";
@@ -175,6 +175,7 @@ declare const MESSAGES: {
175
175
  readonly codex_sync_failed: "Codex.app sync failed: {error}";
176
176
  readonly opened_in_codex: "Opened in Codex.app.";
177
177
  readonly started_new_thread: "Started new thread {threadId}";
178
+ readonly thread_new_started_short: "Starting new thread";
178
179
  readonly thread_new_prompt_short: "Send PWD";
179
180
  readonly thread_new_prompt: "Send the PWD for the new thread. Send \".\" to use the default:\n{cwd}";
180
181
  readonly thread_new_cwd_missing: "PWD does not exist:\n{cwd}\nCreate it and start the new thread there?";
@@ -305,6 +306,7 @@ declare const MESSAGES: {
305
306
  readonly button_reveal: "↗️ Reveal";
306
307
  readonly button_auto: "⚙️ Auto";
307
308
  readonly button_new_thread: "➕ New";
309
+ readonly button_thread_new: "➕";
308
310
  readonly threads_no_matches: "<b>No threads matched</b>\n<code>{searchTerm}</code>";
309
311
  readonly threads_no_recent: "<b>No recent threads.</b>";
310
312
  readonly threads_recent_title: "<b>Recent threads</b>";
@@ -318,9 +320,9 @@ declare const MESSAGES: {
318
320
  readonly button_clear_filter: "🧹 Clear";
319
321
  readonly button_recent_threads: "🕘 Recent";
320
322
  readonly button_archived_threads: "🗄️ Archived";
321
- readonly button_thread_rename: "✏️ Rename";
322
- readonly button_thread_watch: "👀 Watch";
323
- readonly button_thread_archive: "🗑️ Archive/Delete";
323
+ readonly button_thread_rename: "✏️";
324
+ readonly button_thread_watch: "👀";
325
+ readonly button_thread_archive: "🗑️";
324
326
  readonly button_thread_unarchive: "♻️ Unarchive";
325
327
  readonly threads_current: "Current: <b>{title}</b>";
326
328
  readonly where_no_thread_bound: "No thread is currently bound.";
@@ -370,8 +372,8 @@ declare const MESSAGES: {
370
372
  readonly active_turn_message_mode_steer: "Steer current turn";
371
373
  readonly active_turn_message_mode_queue: "Queue next turn";
372
374
  readonly button_fast_on: "⚡ Fast: on";
373
- readonly button_fast_off: "Fast: off";
374
- readonly button_fast_unsupported: "Fast unsupported";
375
+ readonly button_fast_off: "Fast: off";
376
+ readonly button_fast_unsupported: "Fast unsupported";
375
377
  readonly access_preset_read_only: "Read-only";
376
378
  readonly access_preset_default: "Default";
377
379
  readonly access_preset_full_access: "Full access";
@@ -698,8 +700,8 @@ declare const MESSAGES: {
698
700
  readonly auth_add_missing_file: "登录已完成,但没有创建新的 auth 文件:{value}";
699
701
  readonly button_login_device: "🔑 设备登录";
700
702
  readonly button_auth_reload: "🔄 重载 auth";
701
- readonly button_auth_enable: "✅ 启用";
702
- readonly button_auth_disable: "⏸️ 禁用";
703
+ readonly button_auth_enable: "✅";
704
+ readonly button_auth_disable: "⏸️";
703
705
  readonly another_turn_running: "已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。";
704
706
  readonly working: "处理中...";
705
707
  readonly usage_open: "用法:/open <编号>";
@@ -723,6 +725,7 @@ declare const MESSAGES: {
723
725
  readonly codex_sync_failed: "同步到 Codex.app 失败:{error}";
724
726
  readonly opened_in_codex: "已在 Codex.app 中打开。";
725
727
  readonly started_new_thread: "已新建线程 {threadId}";
728
+ readonly thread_new_started_short: "正在新建线程";
726
729
  readonly thread_new_prompt_short: "发送 PWD";
727
730
  readonly thread_new_prompt: "请发送新线程的 PWD。发送 \".\" 使用默认目录:\n{cwd}";
728
731
  readonly thread_new_cwd_missing: "PWD 不存在:\n{cwd}\n是否新建这个目录,并在其中启动新线程?";
@@ -853,6 +856,7 @@ declare const MESSAGES: {
853
856
  readonly button_reveal: "↗️ 打开";
854
857
  readonly button_auto: "⚙️ 自动";
855
858
  readonly button_new_thread: "➕ 新建";
859
+ readonly button_thread_new: "➕";
856
860
  readonly threads_no_matches: "<b>没有匹配的线程</b>\n<code>{searchTerm}</code>";
857
861
  readonly threads_no_recent: "<b>暂无最近线程。</b>";
858
862
  readonly threads_recent_title: "<b>最近线程</b>";
@@ -866,9 +870,9 @@ declare const MESSAGES: {
866
870
  readonly button_clear_filter: "🧹 清除";
867
871
  readonly button_recent_threads: "🕘 最近线程";
868
872
  readonly button_archived_threads: "🗄️ 已归档";
869
- readonly button_thread_rename: "✏️ 重命名";
870
- readonly button_thread_watch: "👀 观察";
871
- readonly button_thread_archive: "🗑️ 归档/删除";
873
+ readonly button_thread_rename: "✏️";
874
+ readonly button_thread_watch: "👀";
875
+ readonly button_thread_archive: "🗑️";
872
876
  readonly button_thread_unarchive: "♻️ 取消归档";
873
877
  readonly threads_current: "当前:<b>{title}</b>";
874
878
  readonly where_no_thread_bound: "当前没有绑定线程。";
@@ -918,8 +922,8 @@ declare const MESSAGES: {
918
922
  readonly active_turn_message_mode_steer: "引导当前回复";
919
923
  readonly active_turn_message_mode_queue: "排队到下一轮";
920
924
  readonly button_fast_on: "⚡ Fast:开";
921
- readonly button_fast_off: "Fast:关";
922
- readonly button_fast_unsupported: "Fast 不支持";
925
+ readonly button_fast_off: "Fast:关";
926
+ readonly button_fast_unsupported: "Fast 不支持";
923
927
  readonly access_preset_read_only: "只读";
924
928
  readonly access_preset_default: "默认";
925
929
  readonly access_preset_full_access: "完全访问";
package/dist/i18n.js CHANGED
@@ -148,8 +148,8 @@ const MESSAGES = {
148
148
  auth_add_missing_file: 'Login completed, but the new auth file was not created: {value}',
149
149
  button_login_device: '🔑 Login',
150
150
  button_auth_reload: '🔄 Reload auth',
151
- button_auth_enable: '✅ Enable',
152
- button_auth_disable: '⏸️ Disable',
151
+ button_auth_enable: '✅',
152
+ button_auth_disable: '⏸️',
153
153
  another_turn_running: 'Another turn is already running. Use /interrupt, /takeover, /queue, or wait.',
154
154
  working: 'Working...',
155
155
  usage_open: 'Usage: /open <n>',
@@ -173,6 +173,7 @@ const MESSAGES = {
173
173
  codex_sync_failed: 'Codex.app sync failed: {error}',
174
174
  opened_in_codex: 'Opened in Codex.app.',
175
175
  started_new_thread: 'Started new thread {threadId}',
176
+ thread_new_started_short: 'Starting new thread',
176
177
  thread_new_prompt_short: 'Send PWD',
177
178
  thread_new_prompt: 'Send the PWD for the new thread. Send "." to use the default:\n{cwd}',
178
179
  thread_new_cwd_missing: 'PWD does not exist:\n{cwd}\nCreate it and start the new thread there?',
@@ -303,6 +304,7 @@ const MESSAGES = {
303
304
  button_reveal: '↗️ Reveal',
304
305
  button_auto: '⚙️ Auto',
305
306
  button_new_thread: '➕ New',
307
+ button_thread_new: '➕',
306
308
  threads_no_matches: '<b>No threads matched</b>\n<code>{searchTerm}</code>',
307
309
  threads_no_recent: '<b>No recent threads.</b>',
308
310
  threads_recent_title: '<b>Recent threads</b>',
@@ -316,9 +318,9 @@ const MESSAGES = {
316
318
  button_clear_filter: '🧹 Clear',
317
319
  button_recent_threads: '🕘 Recent',
318
320
  button_archived_threads: '🗄️ Archived',
319
- button_thread_rename: '✏️ Rename',
320
- button_thread_watch: '👀 Watch',
321
- button_thread_archive: '🗑️ Archive/Delete',
321
+ button_thread_rename: '✏️',
322
+ button_thread_watch: '👀',
323
+ button_thread_archive: '🗑️',
322
324
  button_thread_unarchive: '♻️ Unarchive',
323
325
  threads_current: 'Current: <b>{title}</b>',
324
326
  where_no_thread_bound: 'No thread is currently bound.',
@@ -368,8 +370,8 @@ const MESSAGES = {
368
370
  active_turn_message_mode_steer: 'Steer current turn',
369
371
  active_turn_message_mode_queue: 'Queue next turn',
370
372
  button_fast_on: '⚡ Fast: on',
371
- button_fast_off: 'Fast: off',
372
- button_fast_unsupported: 'Fast unsupported',
373
+ button_fast_off: 'Fast: off',
374
+ button_fast_unsupported: 'Fast unsupported',
373
375
  access_preset_read_only: 'Read-only',
374
376
  access_preset_default: 'Default',
375
377
  access_preset_full_access: 'Full access',
@@ -696,8 +698,8 @@ const MESSAGES = {
696
698
  auth_add_missing_file: '登录已完成,但没有创建新的 auth 文件:{value}',
697
699
  button_login_device: '🔑 设备登录',
698
700
  button_auth_reload: '🔄 重载 auth',
699
- button_auth_enable: '✅ 启用',
700
- button_auth_disable: '⏸️ 禁用',
701
+ button_auth_enable: '✅',
702
+ button_auth_disable: '⏸️',
701
703
  another_turn_running: '已经有一个回复在进行中。请先等待,或使用 /interrupt、/takeover、/queue。',
702
704
  working: '处理中...',
703
705
  usage_open: '用法:/open <编号>',
@@ -721,6 +723,7 @@ const MESSAGES = {
721
723
  codex_sync_failed: '同步到 Codex.app 失败:{error}',
722
724
  opened_in_codex: '已在 Codex.app 中打开。',
723
725
  started_new_thread: '已新建线程 {threadId}',
726
+ thread_new_started_short: '正在新建线程',
724
727
  thread_new_prompt_short: '发送 PWD',
725
728
  thread_new_prompt: '请发送新线程的 PWD。发送 "." 使用默认目录:\n{cwd}',
726
729
  thread_new_cwd_missing: 'PWD 不存在:\n{cwd}\n是否新建这个目录,并在其中启动新线程?',
@@ -851,6 +854,7 @@ const MESSAGES = {
851
854
  button_reveal: '↗️ 打开',
852
855
  button_auto: '⚙️ 自动',
853
856
  button_new_thread: '➕ 新建',
857
+ button_thread_new: '➕',
854
858
  threads_no_matches: '<b>没有匹配的线程</b>\n<code>{searchTerm}</code>',
855
859
  threads_no_recent: '<b>暂无最近线程。</b>',
856
860
  threads_recent_title: '<b>最近线程</b>',
@@ -864,9 +868,9 @@ const MESSAGES = {
864
868
  button_clear_filter: '🧹 清除',
865
869
  button_recent_threads: '🕘 最近线程',
866
870
  button_archived_threads: '🗄️ 已归档',
867
- button_thread_rename: '✏️ 重命名',
868
- button_thread_watch: '👀 观察',
869
- button_thread_archive: '🗑️ 归档/删除',
871
+ button_thread_rename: '✏️',
872
+ button_thread_watch: '👀',
873
+ button_thread_archive: '🗑️',
870
874
  button_thread_unarchive: '♻️ 取消归档',
871
875
  threads_current: '当前:<b>{title}</b>',
872
876
  where_no_thread_bound: '当前没有绑定线程。',
@@ -916,8 +920,8 @@ const MESSAGES = {
916
920
  active_turn_message_mode_steer: '引导当前回复',
917
921
  active_turn_message_mode_queue: '排队到下一轮',
918
922
  button_fast_on: '⚡ Fast:开',
919
- button_fast_off: 'Fast:关',
920
- button_fast_unsupported: 'Fast 不支持',
923
+ button_fast_off: 'Fast:关',
924
+ button_fast_unsupported: 'Fast 不支持',
921
925
  access_preset_read_only: '只读',
922
926
  access_preset_default: '默认',
923
927
  access_preset_full_access: '完全访问',
package/dist/main.js CHANGED
@@ -23,7 +23,16 @@ async function main() {
23
23
  }
24
24
  if (command === 'start') {
25
25
  requireNode24(command);
26
- startService();
26
+ startService('start');
27
+ return;
28
+ }
29
+ if (command === 'restart') {
30
+ requireNode24(command);
31
+ startService('restart');
32
+ return;
33
+ }
34
+ if (command === 'stop') {
35
+ stopService();
27
36
  return;
28
37
  }
29
38
  if (command === 'uninstall-systemd') {
@@ -146,10 +155,10 @@ function initConfig() {
146
155
  console.log(`Created ${envPath}`);
147
156
  console.log('Edit it, then run: foxclaw doctor');
148
157
  }
149
- function startService() {
158
+ function startService(action) {
150
159
  if (!runDoctorChecks()) {
151
160
  console.error('');
152
- console.error('Fix the failed checks above, then run: foxclaw start');
161
+ console.error(`Fix the failed checks above, then run: foxclaw ${action}`);
153
162
  process.exit(1);
154
163
  }
155
164
  if (process.platform === 'darwin') {
@@ -158,6 +167,13 @@ function startService() {
158
167
  }
159
168
  installSystemd();
160
169
  }
170
+ function stopService() {
171
+ if (process.platform === 'darwin') {
172
+ stopLaunchd();
173
+ return;
174
+ }
175
+ stopSystemd();
176
+ }
161
177
  function runDoctorChecks() {
162
178
  const configuredCodexBin = process.env.CODEX_CLI_BIN;
163
179
  const checks = [
@@ -263,6 +279,15 @@ function uninstallSystemd() {
263
279
  spawnChecked('systemctl', ['--user', 'daemon-reload']);
264
280
  console.log(`Removed ${unitPath}`);
265
281
  }
282
+ function stopSystemd() {
283
+ if (!hasCommand('systemctl')) {
284
+ console.error('systemctl not found');
285
+ process.exit(1);
286
+ }
287
+ const unitName = 'foxclaw.service';
288
+ spawnChecked('systemctl', ['--user', 'stop', unitName]);
289
+ console.log(`Stopped ${unitName}`);
290
+ }
266
291
  function installLaunchd() {
267
292
  if (process.platform !== 'darwin') {
268
293
  console.error('launchd install is only available on macOS');
@@ -320,6 +345,19 @@ ${foxclawEnvXml}
320
345
  spawnChecked('launchctl', ['load', plist]);
321
346
  console.log(`Installed ${plist}`);
322
347
  }
348
+ function stopLaunchd() {
349
+ if (process.platform !== 'darwin') {
350
+ console.error('launchd stop is only available on macOS');
351
+ process.exit(1);
352
+ }
353
+ const plist = path.join(process.env.HOME || '', 'Library', 'LaunchAgents', 'app.foxden.foxclaw.plist');
354
+ if (!fs.existsSync(plist)) {
355
+ console.error(`launchd plist not found: ${plist}`);
356
+ process.exit(1);
357
+ }
358
+ spawnChecked('launchctl', ['unload', plist]);
359
+ console.log(`Stopped ${plist}`);
360
+ }
323
361
  function buildServicePath(nodeDir) {
324
362
  const parts = [
325
363
  path.join(process.env.HOME || '', '.local', 'bin'),
@@ -259,13 +259,13 @@ foxclaw status
259
259
  Restart Linux service after changing `.env`:
260
260
 
261
261
  ```bash
262
- foxclaw start
262
+ foxclaw restart
263
263
  ```
264
264
 
265
- Stop Linux service:
265
+ Stop the service:
266
266
 
267
267
  ```bash
268
- systemctl --user stop foxclaw.service
268
+ foxclaw stop
269
269
  ```
270
270
 
271
271
  Uninstall Linux service:
@@ -280,3 +280,7 @@ Update FoxClaw later:
280
280
  npm install -g @foxden-app/foxclaw@latest
281
281
  foxclaw start
282
282
  ```
283
+
284
+ ## Next Step
285
+
286
+ After the first install works, read the [User Manual](./user-manual.md) for `/help`, `/setup`, `/threads`, `/watch`, `/auth`, Codex login, and multi-account auth rotation.
@@ -93,7 +93,7 @@ Check these in order:
93
93
  5. Restart after changing `.env`:
94
94
 
95
95
  ```bash
96
- foxclaw start
96
+ foxclaw restart
97
97
  ```
98
98
 
99
99
  If running foreground mode, stop with `Ctrl+C` and run `foxclaw serve` again.
@@ -145,7 +145,7 @@ systemctl --user disable --now telegram-codex-app-bridge.service
145
145
  Then restart FoxClaw:
146
146
 
147
147
  ```bash
148
- foxclaw start
148
+ foxclaw restart
149
149
  ```
150
150
 
151
151
  ## Codex Or App-Server Fails