cckeep 0.1.0 → 0.1.2
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 +37 -6
- package/README.md +41 -6
- package/bin/cckeep.js +9 -0
- package/package.json +1 -1
- package/src/i18n.js +6 -0
- package/src/scheduler.js +40 -1
package/README.ja.md
CHANGED
|
@@ -28,18 +28,31 @@
|
|
|
28
28
|
## クイックスタート
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
|
|
31
|
+
npm install -g cckeep
|
|
32
|
+
cckeep install
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
バックグラウンドジョブを登録します(macOS は launchd、Linux は systemd user timer)。15秒ごとに確認し、死んでいるものを繋ぎ直します。
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
`npx cckeep install` ではなくグローバルに入れてください。登録されたジョブはインストール先の cckeep を実行しますが、npx のキャッシュは使い捨てです。そこを指すジョブはキャッシュが消された瞬間に、黙って動かなくなります — 監視ツールが持ってはいけない唯一の壊れ方です。そのため `cckeep install` は npx のパスからの登録を拒否します。
|
|
38
|
+
|
|
39
|
+
入れる前に様子を見るだけなら `npx` で構いません。以下の2つは何も変更しません。
|
|
37
40
|
|
|
38
41
|
```sh
|
|
39
|
-
npx cckeep # いま何が見えているか
|
|
42
|
+
npx cckeep # いま何が見えているか
|
|
40
43
|
npx cckeep doctor # tmux・ペイン・スケジューラの確認
|
|
41
44
|
```
|
|
42
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
|
+
|
|
54
|
+
条件が一つあります。**Claude Code が tmux の中で動いていること。** 素のターミナルで起動したセッションには別プロセスから入力を送る手段がなく、どんなツールでも手が出せません。→ [tmux で Claude Code を動かす](#tmux-で-claude-code-を動かす)
|
|
55
|
+
|
|
43
56
|
> 机に戻る手間が省けたなら、⭐ が同じ問題を抱えている人に届く助けになります。
|
|
44
57
|
|
|
45
58
|
## 作業中のセッションには打ち込みません
|
|
@@ -99,13 +112,31 @@ cc() {
|
|
|
99
112
|
esac
|
|
100
113
|
done
|
|
101
114
|
local session="claude-$(basename "$PWD")-$(printf '%s' "$PWD" | cksum | cut -d' ' -f1)"
|
|
102
|
-
if [ -n "$TMUX" ]; then command claude "$@"
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
if [ -n "$TMUX" ]; then command claude "$@"; return; fi
|
|
116
|
+
if ! tmux has-session -t "=$session" 2>/dev/null; then
|
|
117
|
+
tmux new-session -s "$session" -c "$PWD" claude "$@"; return
|
|
105
118
|
fi
|
|
119
|
+
|
|
120
|
+
# tmux セッションは中の claude より長生きするので、そのままアタッチすると
|
|
121
|
+
# 会話が消えた素のシェルに落ちることがある。その場合は会話を呼び戻す。
|
|
122
|
+
local pane_cmd cmd
|
|
123
|
+
pane_cmd=$(tmux list-panes -t "=$session:" -F '#{pane_current_command}' 2>/dev/null | head -1)
|
|
124
|
+
case "$pane_cmd" in
|
|
125
|
+
zsh|bash|sh|fish)
|
|
126
|
+
cmd="claude"
|
|
127
|
+
case " $* " in
|
|
128
|
+
*" -c "*|*" --continue "*|*" -r "*|*" --resume "*|*" --session-id "*) ;;
|
|
129
|
+
*) cmd="$cmd --continue" ;;
|
|
130
|
+
esac
|
|
131
|
+
[ $# -gt 0 ] && cmd="$cmd $*"
|
|
132
|
+
tmux send-keys -t "=$session:" "$cmd" C-m ;;
|
|
133
|
+
esac
|
|
134
|
+
tmux attach-session -t "=$session"
|
|
106
135
|
}
|
|
107
136
|
```
|
|
108
137
|
|
|
138
|
+
`cc -c` と `cc --continue` はそのまま使えます — フラグは Claude Code にそのまま渡ります。上のブロックが埋めるのは、フラグでは救えないケース(tmux セッションだけ残って中の Claude Code が終了している状態)です。
|
|
139
|
+
|
|
109
140
|
あわせて `~/.tmux.conf` に2行必要です。これが無いと tmux 内で Shift+Enter とデスクトップ通知が壊れます([公式の案内](https://code.claude.com/docs/en/terminal-config#configure-tmux))。
|
|
110
141
|
|
|
111
142
|
```sh
|
package/README.md
CHANGED
|
@@ -28,18 +28,33 @@ Either way you find out the same way: you reach for your phone, and the session
|
|
|
28
28
|
## Quick start
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
|
|
31
|
+
npm install -g cckeep
|
|
32
|
+
cckeep install
|
|
32
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
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.
|
|
38
|
+
|
|
39
|
+
To look before installing anything, `npx` is fine — neither of these changes a thing:
|
|
37
40
|
|
|
38
41
|
```sh
|
|
39
|
-
npx cckeep # what it sees right now
|
|
42
|
+
npx cckeep # what it sees right now
|
|
40
43
|
npx cckeep doctor # check tmux, panes, and the scheduler
|
|
41
44
|
```
|
|
42
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
|
+
|
|
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).
|
|
57
|
+
|
|
43
58
|
> If cckeep saved you a walk back to your desk, a ⭐ helps other Remote Control users find it.
|
|
44
59
|
|
|
45
60
|
## It will not type into a session that is working
|
|
@@ -99,13 +114,33 @@ cc() {
|
|
|
99
114
|
esac
|
|
100
115
|
done
|
|
101
116
|
local session="claude-$(basename "$PWD")-$(printf '%s' "$PWD" | cksum | cut -d' ' -f1)"
|
|
102
|
-
if [ -n "$TMUX" ]; then command claude "$@"
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
105
120
|
fi
|
|
121
|
+
|
|
122
|
+
# The tmux session outlives the claude process inside it, so attaching can
|
|
123
|
+
# drop you at a bare shell with the conversation gone. Bring it back.
|
|
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"
|
|
106
137
|
}
|
|
107
138
|
```
|
|
108
139
|
|
|
140
|
+
`cc -c` and `cc --continue` work as you'd expect — the flag is passed straight
|
|
141
|
+
through to Claude Code. The block above covers the case the flag can't: a tmux
|
|
142
|
+
session whose Claude Code process has already exited.
|
|
143
|
+
|
|
109
144
|
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)):
|
|
110
145
|
|
|
111
146
|
```sh
|
package/bin/cckeep.js
CHANGED
|
@@ -130,6 +130,10 @@ async function main() {
|
|
|
130
130
|
}
|
|
131
131
|
case 'install': {
|
|
132
132
|
const res = scheduler.install({ interval: config.interval });
|
|
133
|
+
if (res.kind === 'ephemeral') {
|
|
134
|
+
console.error(t.ephemeral(res.path));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
133
137
|
if (res.kind === 'unsupported') {
|
|
134
138
|
console.log(t.unsupported);
|
|
135
139
|
process.exit(1);
|
|
@@ -155,6 +159,11 @@ async function main() {
|
|
|
155
159
|
['tmux server', bin && tmux.hasServer() ? t.doctorOk : t.doctorFail],
|
|
156
160
|
['claude panes', String(claudePanes.length)],
|
|
157
161
|
['scheduler', scheduler.isInstalled() ? t.doctorOk : t.doctorFail],
|
|
162
|
+
[t.scheduledPath, (() => {
|
|
163
|
+
const cli = scheduler.scheduledCli();
|
|
164
|
+
if (!cli) return '—';
|
|
165
|
+
return existsSync(cli) ? cli : `${cli} ${t.scheduledMissing}`;
|
|
166
|
+
})()],
|
|
158
167
|
['home', homeDir()],
|
|
159
168
|
['config', existsSync(configPath()) ? configPath() : `${configPath()} (defaults)`],
|
|
160
169
|
['state', statePath()],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cckeep",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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": {
|
package/src/i18n.js
CHANGED
|
@@ -21,6 +21,9 @@ const en = {
|
|
|
21
21
|
installFailed: (kind) => `Wrote the ${kind} unit, but could not load it. Load it manually, or run \`cckeep watch\` in a terminal.`,
|
|
22
22
|
uninstalled: (kind) => `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`,
|
|
25
|
+
scheduledPath: 'scheduled cmd',
|
|
26
|
+
scheduledMissing: 'MISSING — run `cckeep install` again',
|
|
24
27
|
noLog: 'No log yet.',
|
|
25
28
|
doctorOk: 'ok',
|
|
26
29
|
doctorFail: 'missing',
|
|
@@ -49,6 +52,9 @@ const ja = {
|
|
|
49
52
|
installFailed: (kind) => `${kind} の定義は書きましたが、読み込みに失敗しました。手動で読み込むか、ターミナルで \`cckeep watch\` を実行してください。`,
|
|
50
53
|
uninstalled: (kind) => `${kind} のジョブを削除しました。`,
|
|
51
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`,
|
|
56
|
+
scheduledPath: '登録先',
|
|
57
|
+
scheduledMissing: '見つかりません — `cckeep install` を再実行してください',
|
|
52
58
|
noLog: 'ログはまだありません。',
|
|
53
59
|
doctorOk: 'ok',
|
|
54
60
|
doctorFail: '未検出',
|
package/src/scheduler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { mkdirSync, writeFileSync, existsSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, unlinkSync } from 'node:fs';
|
|
3
3
|
import { homedir, platform } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,6 +10,16 @@ function cliPath() {
|
|
|
10
10
|
return fileURLToPath(new URL('../bin/cckeep.js', import.meta.url));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* `npx cckeep` runs out of npm's throwaway cache. Baking that path into a
|
|
15
|
+
* launchd job or systemd unit produces a scheduler that works today and stops
|
|
16
|
+
* forever the first time the cache is evicted — silently, which is the one
|
|
17
|
+
* failure this tool must not have. Refuse instead, and ask for a real install.
|
|
18
|
+
*/
|
|
19
|
+
export function isEphemeralPath(p) {
|
|
20
|
+
return /[\\/]_npx[\\/]/.test(p) || /[\\/]\.npm[\\/]_cacache[\\/]/.test(p);
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
function nodePath() {
|
|
14
24
|
return process.execPath;
|
|
15
25
|
}
|
|
@@ -83,6 +93,9 @@ function run(cmd, args) {
|
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
export function install({ interval }) {
|
|
96
|
+
const cli = cliPath();
|
|
97
|
+
if (isEphemeralPath(cli)) return { kind: 'ephemeral', path: cli, ok: false };
|
|
98
|
+
|
|
86
99
|
const os = platform();
|
|
87
100
|
if (os === 'darwin') {
|
|
88
101
|
const p = plistPath();
|
|
@@ -132,3 +145,29 @@ export function isInstalled() {
|
|
|
132
145
|
if (os === 'linux') return existsSync(join(systemdDir(), 'cckeep.timer'));
|
|
133
146
|
return false;
|
|
134
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The cckeep path the installed scheduler will actually run. Read back from the
|
|
151
|
+
* unit rather than recomputed, so `doctor` can catch a job left pointing at a
|
|
152
|
+
* copy that has since been moved or deleted.
|
|
153
|
+
*/
|
|
154
|
+
export function scheduledCli() {
|
|
155
|
+
try {
|
|
156
|
+
const os = platform();
|
|
157
|
+
if (os === 'darwin') {
|
|
158
|
+
const p = plistPath();
|
|
159
|
+
if (!existsSync(p)) return null;
|
|
160
|
+
const xml = readFileSync(p, 'utf8');
|
|
161
|
+
const args = [...xml.matchAll(/<string>([^<]*)<\/string>/g)].map((m) => m[1]);
|
|
162
|
+
return args.find((a) => a.endsWith('cckeep.js')) ?? null;
|
|
163
|
+
}
|
|
164
|
+
if (os === 'linux') {
|
|
165
|
+
const p = join(systemdDir(), 'cckeep.service');
|
|
166
|
+
if (!existsSync(p)) return null;
|
|
167
|
+
const unit = readFileSync(p, 'utf8');
|
|
168
|
+
const line = unit.match(/^ExecStart=(.*)$/m)?.[1] ?? '';
|
|
169
|
+
return line.split(/\s+/).find((a) => a.endsWith('cckeep.js')) ?? null;
|
|
170
|
+
}
|
|
171
|
+
} catch {}
|
|
172
|
+
return null;
|
|
173
|
+
}
|