cckeep 0.1.1 → 0.2.0

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.ja.md CHANGED
@@ -29,12 +29,12 @@
29
29
 
30
30
  ```sh
31
31
  npm install -g cckeep
32
- cckeep install
32
+ cckeep enable
33
33
  ```
34
34
 
35
- バックグラウンドジョブを登録します(macOS は launchd、Linux は systemd user timer)。15秒ごとに確認し、死んでいるものを繋ぎ直します。
35
+ `npm install` は CLI を `PATH` に置くだけで、常駐はまだ始まりません。`cckeep enable` がバックグラウンドジョブを登録します(macOS は launchd、Linux は systemd user timer)。15秒ごとに確認し、死んでいるものを繋ぎ直します。
36
36
 
37
- `npx cckeep install` ではなくグローバルに入れてください。登録されたジョブはインストール先の cckeep を実行しますが、npx のキャッシュは使い捨てです。そこを指すジョブはキャッシュが消された瞬間に、黙って動かなくなります — 監視ツールが持ってはいけない唯一の壊れ方です。そのため `cckeep install` は npx のパスからの登録を拒否します。
37
+ `npx cckeep enable` ではなくグローバルに入れてください。登録されたジョブはインストール先の cckeep を実行しますが、npx のキャッシュは使い捨てです。そこを指すジョブはキャッシュが消された瞬間に、黙って動かなくなります — 監視ツールが持ってはいけない唯一の壊れ方です。そのため `cckeep enable` は npx のパスからの登録を拒否します。
38
38
 
39
39
  入れる前に様子を見るだけなら `npx` で構いません。以下の2つは何も変更しません。
40
40
 
@@ -43,6 +43,14 @@ npx cckeep # いま何が見えているか
43
43
  npx cckeep doctor # tmux・ペイン・スケジューラの確認
44
44
  ```
45
45
 
46
+ **インストール直後に `command not found: cckeep` と出たら**、shim 方式のバージョン管理ツールを使っています。`nodenv` や `asdf` は新しく入った実行ファイルを PATH に出すのに rehash が必要で、`nvm` はシェルを開き直す必要があります。
47
+
48
+ ```sh
49
+ nodenv rehash # nodenv
50
+ asdf reshim nodejs # asdf
51
+ # nvm: 新しいシェルを開くだけ
52
+ ```
53
+
46
54
  条件が一つあります。**Claude Code が tmux の中で動いていること。** 素のターミナルで起動したセッションには別プロセスから入力を送る手段がなく、どんなツールでも手が出せません。→ [tmux で Claude Code を動かす](#tmux-で-claude-code-を動かす)
47
55
 
48
56
  > 机に戻る手間が省けたなら、⭐ が同じ問題を抱えている人に届く助けになります。
@@ -77,14 +85,16 @@ npx cckeep doctor # tmux・ペイン・スケジューラの確認
77
85
  cckeep # 状態表示: Claude Code のペインごとに1行
78
86
  cckeep watch # スケジュール登録せずフォアグラウンドで実行
79
87
  cckeep once # 1回だけ実行(スケジューラが叩くもの)
80
- cckeep install # バックグラウンドジョブを登録
81
- cckeep uninstall # 解除
88
+ cckeep enable # バックグラウンドでの監視を開始
89
+ cckeep disable # 監視を停止
82
90
  cckeep doctor # tmux・ペイン・スケジューラ・各パス
83
91
  cckeep logs # これまでの動作
84
92
  ```
85
93
 
86
94
  オプション: `--dry-run`、`--json`、`--interval <秒>`、`--lang en|ja`(`LANG` から自動判定)。
87
95
 
96
+ `install` / `uninstall` は `enable` / `disable` のエイリアスとして引き続き使えます。
97
+
88
98
  ## tmux で Claude Code を動かす
89
99
 
90
100
  cckeep は tmux ペインを読み、そこに入力します。動いている Claude Code セッションに別プロセスから届く経路はこれだけで、だからこそ tmux 内での起動が必要です。プロセスの再起動は代替になりません — 会話が終わってしまい、守りたかったものそのものを失うからです。
@@ -104,13 +114,31 @@ cc() {
104
114
  esac
105
115
  done
106
116
  local session="claude-$(basename "$PWD")-$(printf '%s' "$PWD" | cksum | cut -d' ' -f1)"
107
- if [ -n "$TMUX" ]; then command claude "$@"
108
- elif tmux has-session -t "=$session" 2>/dev/null; then tmux attach-session -t "=$session"
109
- else tmux new-session -s "$session" -c "$PWD" claude "$@"
117
+ if [ -n "$TMUX" ]; then command claude "$@"; return; fi
118
+ if ! tmux has-session -t "=$session" 2>/dev/null; then
119
+ tmux new-session -s "$session" -c "$PWD" claude "$@"; return
110
120
  fi
121
+
122
+ # tmux セッションは中の claude より長生きするので、そのままアタッチすると
123
+ # 会話が消えた素のシェルに落ちることがある。その場合は会話を呼び戻す。
124
+ local pane_cmd cmd
125
+ pane_cmd=$(tmux list-panes -t "=$session:" -F '#{pane_current_command}' 2>/dev/null | head -1)
126
+ case "$pane_cmd" in
127
+ zsh|bash|sh|fish)
128
+ cmd="claude"
129
+ case " $* " in
130
+ *" -c "*|*" --continue "*|*" -r "*|*" --resume "*|*" --session-id "*) ;;
131
+ *) cmd="$cmd --continue" ;;
132
+ esac
133
+ [ $# -gt 0 ] && cmd="$cmd $*"
134
+ tmux send-keys -t "=$session:" "$cmd" C-m ;;
135
+ esac
136
+ tmux attach-session -t "=$session"
111
137
  }
112
138
  ```
113
139
 
140
+ `cc -c` と `cc --continue` はそのまま使えます — フラグは Claude Code にそのまま渡ります。上のブロックが埋めるのは、フラグでは救えないケース(tmux セッションだけ残って中の Claude Code が終了している状態)です。
141
+
114
142
  あわせて `~/.tmux.conf` に2行必要です。これが無いと tmux 内で Shift+Enter とデスクトップ通知が壊れます([公式の案内](https://code.claude.com/docs/en/terminal-config#configure-tmux))。
115
143
 
116
144
  ```sh
@@ -136,7 +164,7 @@ Ctrl+B の衝突は対処不要です。Claude Code は tmux を検出して、
136
164
  }
137
165
  ```
138
166
 
139
- - `interval` — 巡回間隔(秒)。`install` が登録する間隔でもある
167
+ - `interval` — 巡回間隔(秒)。`enable` が登録する間隔でもある
140
168
  - `cooldown` — 同じペインに再度手を出せるまでの秒数
141
169
  - `stuckLimit` — `reconnecting` が何回続いたら固着とみなすか
142
170
  - `missLimit` — 表示のあったペインが何回無表示なら繋ぎ直すか
package/README.md CHANGED
@@ -29,12 +29,12 @@ Either way you find out the same way: you reach for your phone, and the session
29
29
 
30
30
  ```sh
31
31
  npm install -g cckeep
32
- cckeep install
32
+ cckeep enable
33
33
  ```
34
34
 
35
- That registers a background job — launchd on macOS, a systemd user timer on Linux — that checks every 15 seconds and re-arms whatever went dead.
35
+ `npm install` only puts the CLI on your `PATH`; `cckeep enable` is the step that registers a background job — launchd on macOS, a systemd user timer on Linux — that checks every 15 seconds and re-arms whatever went dead.
36
36
 
37
- Install it globally rather than running `npx cckeep install`. The scheduled job runs cckeep from wherever it was installed, and npx's cache is throwaway: a job pointing into it keeps working until the cache is cleared and then stops, silently — the one failure a watchdog must not have. `cckeep install` refuses to schedule from an npx path for that reason.
37
+ Install it globally rather than running `npx cckeep enable`. The scheduled job runs cckeep from wherever it was installed, and npx's cache is throwaway: a job pointing into it keeps working until the cache is cleared and then stops, silently — the one failure a watchdog must not have. `cckeep enable` refuses to schedule from an npx path for that reason.
38
38
 
39
39
  To look before installing anything, `npx` is fine — neither of these changes a thing:
40
40
 
@@ -43,6 +43,16 @@ npx cckeep # what it sees right now
43
43
  npx cckeep doctor # check tmux, panes, and the scheduler
44
44
  ```
45
45
 
46
+ **`command not found: cckeep` right after installing?** You are on a shim-based
47
+ version manager. `nodenv` and `rbenv`-style setups need a rehash before a newly
48
+ installed binary appears on `PATH`, and `nvm` needs a new shell:
49
+
50
+ ```sh
51
+ nodenv rehash # nodenv
52
+ asdf reshim nodejs # asdf
53
+ # nvm: just open a new shell
54
+ ```
55
+
46
56
  One requirement: **Claude Code has to be running inside tmux.** A session started in a bare terminal cannot be reached from another process, so there is nothing any tool can do for it. See [Running Claude Code in tmux](#running-claude-code-in-tmux).
47
57
 
48
58
  > If cckeep saved you a walk back to your desk, a ⭐ helps other Remote Control users find it.
@@ -77,14 +87,16 @@ This is the whole design problem. A watchdog that types into your terminal on a
77
87
  cckeep # status: one line per Claude Code pane
78
88
  cckeep watch # run in the foreground instead of scheduling
79
89
  cckeep once # a single pass — what the scheduler runs
80
- cckeep install # register the background job
81
- cckeep uninstall # remove it
90
+ cckeep enable # start checking in the background
91
+ cckeep disable # stop checking
82
92
  cckeep doctor # tmux, panes, scheduler, paths
83
93
  cckeep logs # what it has done
84
94
  ```
85
95
 
86
96
  Options: `--dry-run`, `--json`, `--interval <s>`, `--lang en|ja` (auto-detected from `LANG`).
87
97
 
98
+ `install` and `uninstall` still work as aliases for `enable` and `disable`.
99
+
88
100
  ## Running Claude Code in tmux
89
101
 
90
102
  cckeep reads and types into tmux panes. That is the only channel a separate process has into a live Claude Code session — and it is why the session must be started inside tmux. Restarting the process is not an alternative: it would end the conversation, which is exactly what you are trying to save.
@@ -104,13 +116,33 @@ cc() {
104
116
  esac
105
117
  done
106
118
  local session="claude-$(basename "$PWD")-$(printf '%s' "$PWD" | cksum | cut -d' ' -f1)"
107
- if [ -n "$TMUX" ]; then command claude "$@"
108
- elif tmux has-session -t "=$session" 2>/dev/null; then tmux attach-session -t "=$session"
109
- else tmux new-session -s "$session" -c "$PWD" claude "$@"
119
+ if [ -n "$TMUX" ]; then command claude "$@"; return; fi
120
+ if ! tmux has-session -t "=$session" 2>/dev/null; then
121
+ tmux new-session -s "$session" -c "$PWD" claude "$@"; return
110
122
  fi
123
+
124
+ # The tmux session outlives the claude process inside it, so attaching can
125
+ # drop you at a bare shell with the conversation gone. Bring it back.
126
+ local pane_cmd cmd
127
+ pane_cmd=$(tmux list-panes -t "=$session:" -F '#{pane_current_command}' 2>/dev/null | head -1)
128
+ case "$pane_cmd" in
129
+ zsh|bash|sh|fish)
130
+ cmd="claude"
131
+ case " $* " in
132
+ *" -c "*|*" --continue "*|*" -r "*|*" --resume "*|*" --session-id "*) ;;
133
+ *) cmd="$cmd --continue" ;;
134
+ esac
135
+ [ $# -gt 0 ] && cmd="$cmd $*"
136
+ tmux send-keys -t "=$session:" "$cmd" C-m ;;
137
+ esac
138
+ tmux attach-session -t "=$session"
111
139
  }
112
140
  ```
113
141
 
142
+ `cc -c` and `cc --continue` work as you'd expect — the flag is passed straight
143
+ through to Claude Code. The block above covers the case the flag can't: a tmux
144
+ session whose Claude Code process has already exited.
145
+
114
146
  Claude Code also needs two lines in `~/.tmux.conf`, or Shift+Enter and desktop notifications break inside tmux ([official guidance](https://code.claude.com/docs/en/terminal-config#configure-tmux)):
115
147
 
116
148
  ```sh
@@ -136,7 +168,7 @@ Defaults are tuned so you never notice it. Override in `~/.cckeep/config.json`,
136
168
  }
137
169
  ```
138
170
 
139
- - `interval` — seconds between passes (also what `install` schedules)
171
+ - `interval` — seconds between passes (also what `enable` schedules)
140
172
  - `cooldown` — seconds before the same pane may be acted on again
141
173
  - `stuckLimit` — checks in `reconnecting` before the bridge is treated as wedged
142
174
  - `missLimit` — checks with no indicator before re-arming a pane that had one
package/bin/cckeep.js CHANGED
@@ -7,6 +7,7 @@ import { logPath, statePath, homeDir } from '../src/state.js';
7
7
  import * as tmux from '../src/tmux.js';
8
8
  import * as scheduler from '../src/scheduler.js';
9
9
  import { pickLang, strings } from '../src/i18n.js';
10
+ import { resolveCommand } from '../src/commands.js';
10
11
 
11
12
  const HELP = `cckeep — keep Claude Code Remote Control from silently going dead
12
13
 
@@ -17,11 +18,14 @@ COMMANDS
17
18
  status what cckeep sees right now (default)
18
19
  watch run in the foreground, one pass every --interval seconds
19
20
  once a single pass — what the scheduler runs
20
- install register a background job (launchd on macOS, systemd on Linux)
21
- uninstall remove it
21
+ enable start checking in the background (launchd / systemd user timer)
22
+ disable stop checking
22
23
  doctor check tmux, Claude Code panes, and the scheduler
23
24
  logs show recent actions
24
25
 
26
+ "npm install -g cckeep" only puts this CLI on your PATH. "cckeep enable" is
27
+ what registers the background job. (install / uninstall still work as aliases.)
28
+
25
29
  OPTIONS
26
30
  --interval <s> seconds between passes (default 15)
27
31
  --dry-run report what would be sent, send nothing
@@ -107,7 +111,7 @@ async function main() {
107
111
  process.exit(1);
108
112
  }
109
113
 
110
- switch (opts.command ?? 'status') {
114
+ switch (resolveCommand(opts.command)) {
111
115
  case 'status': {
112
116
  const out = await runPass({ config, dryRun: true });
113
117
  render(out, t, opts);
@@ -128,7 +132,7 @@ async function main() {
128
132
  await new Promise((r) => setTimeout(r, config.interval * 1000));
129
133
  }
130
134
  }
131
- case 'install': {
135
+ case 'enable': {
132
136
  const res = scheduler.install({ interval: config.interval });
133
137
  if (res.kind === 'ephemeral') {
134
138
  console.error(t.ephemeral(res.path));
@@ -138,14 +142,14 @@ async function main() {
138
142
  console.log(t.unsupported);
139
143
  process.exit(1);
140
144
  }
141
- console.log(res.ok ? t.installed(res.kind, res.path) : t.installFailed(res.kind));
145
+ console.log(res.ok ? t.enabled(res.kind, res.path) : t.enableFailed(res.kind));
142
146
  if (!res.ok) process.exit(1);
143
147
  break;
144
148
  }
145
- case 'uninstall': {
149
+ case 'disable': {
146
150
  const res = scheduler.uninstall();
147
151
  if (res.kind === 'unsupported') return console.log(t.unsupported);
148
- console.log(t.uninstalled(res.kind));
152
+ console.log(t.disabled(res.kind));
149
153
  break;
150
154
  }
151
155
  case 'doctor': {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cckeep",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Keeps Claude Code Remote Control from silently going dead. Re-arms the sessions its 31-second retry budget gives up on — local, tmux-native, zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `npm install -g cckeep` puts the CLI on your PATH; registering the background
3
+ * job is a separate, deliberate step. Naming that step `install` too made the
4
+ * quick start read as if you were installing twice, so the scheduler verbs are
5
+ * `enable` / `disable` — the same words systemctl uses for the same idea.
6
+ *
7
+ * The old names stay as aliases: they shipped in 0.1.x and in the plists people
8
+ * already generated, and breaking them buys nothing.
9
+ */
10
+ export const ALIASES = {
11
+ install: 'enable',
12
+ uninstall: 'disable',
13
+ };
14
+
15
+ export const COMMANDS = new Set([
16
+ 'status',
17
+ 'watch',
18
+ 'once',
19
+ 'enable',
20
+ 'disable',
21
+ 'doctor',
22
+ 'logs',
23
+ 'help',
24
+ 'version',
25
+ ]);
26
+
27
+ /** Map whatever the user typed onto a canonical command. */
28
+ export function resolveCommand(input) {
29
+ const name = input ?? 'status';
30
+ return ALIASES[name] ?? name;
31
+ }
32
+
33
+ export function isKnownCommand(input) {
34
+ return COMMANDS.has(resolveCommand(input));
35
+ }
package/src/config.js CHANGED
@@ -5,7 +5,7 @@ import { DEFAULTS } from './detect.js';
5
5
 
6
6
  export const BASE = {
7
7
  ...DEFAULTS,
8
- /** Seconds between passes in watch mode, and what `install` schedules. */
8
+ /** Seconds between passes in watch mode, and what `enable` schedules. */
9
9
  interval: 15,
10
10
  /** Milliseconds between the two captures of the idle check. */
11
11
  settle: 2000,
package/src/i18n.js CHANGED
@@ -17,11 +17,11 @@ const en = {
17
17
  wouldRearm: 'would re-arm',
18
18
  wouldConfirm: 'would cycle bridge',
19
19
  watching: (n) => `Watching every ${n}s. Ctrl+C to stop.`,
20
- installed: (kind, path) => `Installed (${kind}): ${path}`,
21
- installFailed: (kind) => `Wrote the ${kind} unit, but could not load it. Load it manually, or run \`cckeep watch\` in a terminal.`,
22
- uninstalled: (kind) => `Removed the ${kind} job.`,
20
+ enabled: (kind, path) => `Enabled (${kind}): ${path}`,
21
+ enableFailed: (kind) => `Wrote the ${kind} unit, but could not load it. Load it manually, or run \`cckeep watch\` in a terminal.`,
22
+ disabled: (kind) => `Disabled — removed the ${kind} job.`,
23
23
  unsupported: 'Automatic scheduling supports macOS (launchd) and Linux (systemd user timers). Elsewhere, run `cckeep watch` under your own supervisor.',
24
- ephemeral: (p) => `Refusing to schedule: this copy of cckeep lives in npm's throwaway npx cache\n ${p}\nA scheduled job pointing there stops working the moment the cache is cleared — silently. Install it properly first:\n\n npm install -g cckeep && cckeep install\n`,
24
+ ephemeral: (p) => `Refusing to schedule: this copy of cckeep lives in npm's throwaway npx cache\n ${p}\nA scheduled job pointing there stops working the moment the cache is cleared — silently. Install it properly first:\n\n npm install -g cckeep && cckeep enable\n`,
25
25
  scheduledPath: 'scheduled cmd',
26
26
  scheduledMissing: 'MISSING — run `cckeep install` again',
27
27
  noLog: 'No log yet.',
@@ -48,11 +48,11 @@ const ja = {
48
48
  wouldRearm: '再接続を送信する',
49
49
  wouldConfirm: 'ブリッジを張り直す',
50
50
  watching: (n) => `${n} 秒ごとに監視します。Ctrl+C で停止。`,
51
- installed: (kind, path) => `登録しました (${kind}): ${path}`,
52
- installFailed: (kind) => `${kind} の定義は書きましたが、読み込みに失敗しました。手動で読み込むか、ターミナルで \`cckeep watch\` を実行してください。`,
53
- uninstalled: (kind) => `${kind} のジョブを削除しました。`,
51
+ enabled: (kind, path) => `有効化しました (${kind}): ${path}`,
52
+ enableFailed: (kind) => `${kind} の定義は書きましたが、読み込みに失敗しました。手動で読み込むか、ターミナルで \`cckeep watch\` を実行してください。`,
53
+ disabled: (kind) => `無効化しました — ${kind} のジョブを削除しました。`,
54
54
  unsupported: '自動スケジュール登録は macOS (launchd) と Linux (systemd user timer) に対応しています。それ以外では `cckeep watch` を任意の常駐手段で動かしてください。',
55
- ephemeral: (p) => `登録を中止しました: この cckeep は npm の使い捨て npx キャッシュ内にあります\n ${p}\nここを指すジョブは、キャッシュが消された瞬間に黙って動かなくなります。先に正式にインストールしてください:\n\n npm install -g cckeep && cckeep install\n`,
55
+ ephemeral: (p) => `登録を中止しました: この cckeep は npm の使い捨て npx キャッシュ内にあります\n ${p}\nここを指すジョブは、キャッシュが消された瞬間に黙って動かなくなります。先に正式にインストールしてください:\n\n npm install -g cckeep && cckeep enable\n`,
56
56
  scheduledPath: '登録先',
57
57
  scheduledMissing: '見つかりません — `cckeep install` を再実行してください',
58
58
  noLog: 'ログはまだありません。',