@haposoft/cafekit 0.11.11 → 0.12.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/bin/install.js +4 -0
- package/bin/lib/context.js +4 -1
- package/bin/lib/i18n.js +33 -0
- package/bin/lib/rtk-run.js +28 -0
- package/bin/phases/setup-rtk.js +159 -0
- package/bin/phases/summary.js +4 -0
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -37,6 +37,7 @@ const { checkVersions } = require('./lib/version-check');
|
|
|
37
37
|
const { ensureGitignore } = require('./phases/root-config');
|
|
38
38
|
const { runPostInstall } = require('./phases/post-install');
|
|
39
39
|
const { setupSkillDeps } = require('./phases/skills-setup');
|
|
40
|
+
const { setupRtk } = require('./phases/setup-rtk');
|
|
40
41
|
const { printSummary } = require('./phases/summary');
|
|
41
42
|
|
|
42
43
|
/** Install a single platform: payload + runtime + metadata, under one spinner. */
|
|
@@ -91,6 +92,8 @@ Options:
|
|
|
91
92
|
-u, --upgrade, -f, --force Alias of --force-overwrite
|
|
92
93
|
--with-skills-deps Install skill dependencies (Python venv + pip, npm,
|
|
93
94
|
Chromium/Playwright). Otherwise prompted interactively.
|
|
95
|
+
--with-rtk Install the rtk token-saver (binary + Claude Code hook).
|
|
96
|
+
Otherwise prompted interactively.
|
|
94
97
|
-y, --yes Non-interactive: skip prompts, use defaults (CI)
|
|
95
98
|
-h, --help Show this help
|
|
96
99
|
-v, --version Print version
|
|
@@ -166,6 +169,7 @@ async function main() {
|
|
|
166
169
|
ensureGitignore(ctx);
|
|
167
170
|
await runPostInstall(ctx);
|
|
168
171
|
await setupSkillDeps(ctx);
|
|
172
|
+
await setupRtk(ctx);
|
|
169
173
|
printSummary(ctx);
|
|
170
174
|
|
|
171
175
|
if (!ctx.dryRun) backup.prune(3);
|
package/bin/lib/context.js
CHANGED
|
@@ -185,7 +185,8 @@ function parseInstallerArgs(argv) {
|
|
|
185
185
|
forceOverwrite: false,
|
|
186
186
|
dryRun: false,
|
|
187
187
|
yes: false,
|
|
188
|
-
withSkillsDeps: false
|
|
188
|
+
withSkillsDeps: false,
|
|
189
|
+
withRtk: false
|
|
189
190
|
};
|
|
190
191
|
|
|
191
192
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -199,6 +200,8 @@ function parseInstallerArgs(argv) {
|
|
|
199
200
|
args.yes = true;
|
|
200
201
|
} else if (arg === '--with-skills-deps') {
|
|
201
202
|
args.withSkillsDeps = true;
|
|
203
|
+
} else if (arg === '--with-rtk') {
|
|
204
|
+
args.withRtk = true;
|
|
202
205
|
} else if (arg === '--lang') {
|
|
203
206
|
args.lang = argv[++i];
|
|
204
207
|
} else if (arg.startsWith('--lang=')) {
|
package/bin/lib/i18n.js
CHANGED
|
@@ -59,6 +59,17 @@ const MESSAGES = {
|
|
|
59
59
|
// Skills / deps setup
|
|
60
60
|
skillDepsConfirm: 'Install skill dependencies? (Python venv, pip, npm, Chromium — takes a few minutes)',
|
|
61
61
|
skillsSkipped: 'Skill dependencies skipped.\n Run later: npx @haposoft/cafekit --with-skills-deps',
|
|
62
|
+
|
|
63
|
+
// rtk token-saver setup
|
|
64
|
+
rtkConfirm: 'Install the rtk token-saver? (compacts git/grep/ls/build/test output for Claude Code Bash commands)',
|
|
65
|
+
rtkInstalling: 'Installing rtk via {method}',
|
|
66
|
+
rtkInstalled: 'rtk installed',
|
|
67
|
+
rtkInstallFailed: 'Could not install rtk — skipping (non-fatal). Install manually later: https://github.com/rtk-ai/rtk',
|
|
68
|
+
rtkInitFailed: 'rtk init -g failed: {reason} — hook not registered (non-fatal)',
|
|
69
|
+
rtkNeedsJq: 'rtk hook needs jq to run. Install jq, then the hook activates automatically.',
|
|
70
|
+
rtkFailed: 'rtk setup error: {reason} — skipped (non-fatal)',
|
|
71
|
+
rtkSkipped: 'rtk token-saver skipped.\n Run later: npx @haposoft/cafekit --with-rtk',
|
|
72
|
+
rtkSummary: 'rtk token-saver: hook registered for Claude Code Bash commands',
|
|
62
73
|
venvCreating: 'Setting up Python environment',
|
|
63
74
|
venvReady: 'Python environment ready',
|
|
64
75
|
venvFailed: 'Could not create Python environment — check your Python 3 installation.',
|
|
@@ -165,6 +176,17 @@ const MESSAGES = {
|
|
|
165
176
|
|
|
166
177
|
skillDepsConfirm: 'スキルの依存関係をインストールしますか?(Python venv、pip、npm、Chromium — 数分かかります)',
|
|
167
178
|
skillsSkipped: 'スキルの依存関係をスキップしました。\n 後で実行: npx @haposoft/cafekit --with-skills-deps',
|
|
179
|
+
|
|
180
|
+
// rtk token-saver setup
|
|
181
|
+
rtkConfirm: 'rtk トークンセーバーをインストールしますか?(Claude Code の Bash コマンドの git/grep/ls/build/test 出力を圧縮します)',
|
|
182
|
+
rtkInstalling: '{method} で rtk をインストール中',
|
|
183
|
+
rtkInstalled: 'rtk をインストールしました',
|
|
184
|
+
rtkInstallFailed: 'rtk をインストールできませんでした — スキップします(致命的ではありません)。後で手動でインストール: https://github.com/rtk-ai/rtk',
|
|
185
|
+
rtkInitFailed: 'rtk init -g が失敗しました: {reason} — フックは未登録です(致命的ではありません)',
|
|
186
|
+
rtkNeedsJq: 'rtk フックの実行には jq が必要です。jq をインストールするとフックが自動的に有効になります。',
|
|
187
|
+
rtkFailed: 'rtk セットアップエラー: {reason} — スキップしました(致命的ではありません)',
|
|
188
|
+
rtkSkipped: 'rtk トークンセーバーをスキップしました。\n 後で実行: npx @haposoft/cafekit --with-rtk',
|
|
189
|
+
rtkSummary: 'rtk トークンセーバー: Claude Code の Bash コマンド用にフックを登録しました',
|
|
168
190
|
venvCreating: 'Python 環境を準備しています',
|
|
169
191
|
venvReady: 'Python 環境の準備ができました',
|
|
170
192
|
venvFailed: 'Python 環境を作成できませんでした — Python 3 のインストールを確認してください。',
|
|
@@ -268,6 +290,17 @@ const MESSAGES = {
|
|
|
268
290
|
|
|
269
291
|
skillDepsConfirm: 'Cài dependencies cho skill ngay? (Python venv, pip, npm, Chromium — mất vài phút)',
|
|
270
292
|
skillsSkipped: 'Đã bỏ qua dependencies skill.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-skills-deps',
|
|
293
|
+
|
|
294
|
+
// rtk token-saver setup
|
|
295
|
+
rtkConfirm: 'Cài rtk token-saver? (rút gọn output git/grep/ls/build/test cho các lệnh Bash của Claude Code)',
|
|
296
|
+
rtkInstalling: 'Đang cài rtk qua {method}',
|
|
297
|
+
rtkInstalled: 'Đã cài rtk',
|
|
298
|
+
rtkInstallFailed: 'Không thể cài rtk — bỏ qua (không nghiêm trọng). Cài thủ công sau: https://github.com/rtk-ai/rtk',
|
|
299
|
+
rtkInitFailed: 'rtk init -g thất bại: {reason} — hook chưa được đăng ký (không nghiêm trọng)',
|
|
300
|
+
rtkNeedsJq: 'Hook của rtk cần jq để chạy. Cài jq thì hook sẽ tự kích hoạt.',
|
|
301
|
+
rtkFailed: 'Lỗi cài rtk: {reason} — đã bỏ qua (không nghiêm trọng)',
|
|
302
|
+
rtkSkipped: 'Đã bỏ qua rtk token-saver.\n Cài sau bằng lệnh: npx @haposoft/cafekit --with-rtk',
|
|
303
|
+
rtkSummary: 'rtk token-saver: đã đăng ký hook cho các lệnh Bash của Claude Code',
|
|
271
304
|
venvCreating: 'Đang thiết lập môi trường Python',
|
|
272
305
|
venvReady: 'Môi trường Python đã sẵn sàng',
|
|
273
306
|
venvFailed: 'Không thể tạo môi trường Python — hãy kiểm tra cài đặt Python 3.',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local captured-spawnSync helper for the rtk install phase.
|
|
3
|
+
*
|
|
4
|
+
* Why local: `lib/skill-deps.js` does not export a `run(cmd, args)` helper
|
|
5
|
+
* (it uses `runAsync` internally and never re-exports it). The rtk phase
|
|
6
|
+
* needs a synchronous, captured spawnSync (R2.2, R4.2) that returns
|
|
7
|
+
* `{ ok, status, stdout, stderr }` — same shape as `skill-deps.runAsync` but
|
|
8
|
+
* blocking. KISS: one 20-line module, no dependency on `lib/skill-deps.js`
|
|
9
|
+
* (which is skill-domain and not a generic command runner).
|
|
10
|
+
*
|
|
11
|
+
* @param {string} cmd
|
|
12
|
+
* @param {string[]} args
|
|
13
|
+
* @param {object} [opts] — passed through to spawnSync (e.g. { timeout })
|
|
14
|
+
* @returns {{ ok: boolean, status: number|null, stdout: string, stderr: string }}
|
|
15
|
+
*/
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
function run(cmd, args, opts = {}) {
|
|
19
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
20
|
+
return {
|
|
21
|
+
ok: !r.error && r.status === 0,
|
|
22
|
+
status: r.status,
|
|
23
|
+
stdout: r.stdout || '',
|
|
24
|
+
stderr: r.stderr || ''
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { run };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* setup-rtk — opt-in install of the rtk token-saver and its Claude Code hook.
|
|
3
|
+
*
|
|
4
|
+
* Phase order (per specs/rtk-installer-integration/design.md):
|
|
5
|
+
* shouldRunRtk → ensureRtkBinary → `rtk init -g` → jq precheck → manifest
|
|
6
|
+
*
|
|
7
|
+
* R1-01 wired the consent gate (`shouldRunRtk`) and a thin `setupRtk` wrapper.
|
|
8
|
+
* R2-01 adds the orchestrator: `ensureRtkBinary` (detect/install), the
|
|
9
|
+
* `rtk init -g` hook registration, the `jq` precheck, and the manifest write.
|
|
10
|
+
*
|
|
11
|
+
* Non-fatal invariant (R2.7, R4.1): the entire `setupRtk` body is wrapped in
|
|
12
|
+
* try/catch. Any throw / non-zero exit / timeout is logged as a warning via
|
|
13
|
+
* `ctx.t('rtkFailed')` (with `{reason}` interpolation) and the cafekit
|
|
14
|
+
* install continues with its existing exit code. Mirrors the non-fatal
|
|
15
|
+
* contract of `phases/skills-setup.js`.
|
|
16
|
+
*
|
|
17
|
+
* Idempotency (R2.1, R2.6): re-runs see `hasCmd('rtk')` short-circuit the
|
|
18
|
+
* binary install path and re-invoke `rtk init -g` (rtk's own command handles
|
|
19
|
+
* its hook idempotency). The in-memory `ctx.rtkSetupRan` flag records that
|
|
20
|
+
* setup executed, so downstream consumers (R3-01 summary line) can read it.
|
|
21
|
+
*
|
|
22
|
+
* Cross-OS install (validated 2026-06-15): best-effort order
|
|
23
|
+
* (1) official prebuilt install script (pinned source)
|
|
24
|
+
* (2) `cargo install rtk` when `hasCmd('cargo')`
|
|
25
|
+
* (3) skip + warn
|
|
26
|
+
* covers macOS/Linux dev machines (cargo fall-through) and CI (skip+warn).
|
|
27
|
+
*
|
|
28
|
+
* No timeout for `cargo install rtk` (validated 2026-06-15): user accepted
|
|
29
|
+
* default `spawnSync` timeout; non-fatal guarantee holds via try/catch.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const dep = require('../lib/skill-deps');
|
|
33
|
+
const rtkRun = require('../lib/rtk-run');
|
|
34
|
+
const { hasCmd } = dep;
|
|
35
|
+
const { run } = rtkRun;
|
|
36
|
+
|
|
37
|
+
const RTK_INIT_TIMEOUT_MS = 30_000; // `rtk init -g` is a fast global-hook write
|
|
38
|
+
const RTK_PREBUILT_TIMEOUT_MS = 120_000; // prebuilt install: curl download + sh pipe; slow networks need headroom
|
|
39
|
+
const RTK_PREBUILT_INSTALL_URL =
|
|
40
|
+
'https://raw.githubusercontent.com/rtk-ai/rtk/main/install.sh';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detect rtk binary, installing it if absent. Pure function over `ctx`.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} ctx
|
|
46
|
+
* @returns {'present'|'installed'|'absent'}
|
|
47
|
+
*/
|
|
48
|
+
function ensureRtkBinary(ctx) {
|
|
49
|
+
if (hasCmd('rtk')) return 'present';
|
|
50
|
+
return installRtkBinary(ctx) ? 'installed' : 'absent';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort rtk install. Tries the official prebuilt install script first
|
|
55
|
+
* (R5.2: surface source), then falls back to `cargo install rtk` if cargo is
|
|
56
|
+
* available, otherwise returns false (R2.3 non-fatal skip).
|
|
57
|
+
*
|
|
58
|
+
* @param {object} ctx
|
|
59
|
+
* @returns {boolean} true on success, false on failure or no method available
|
|
60
|
+
*/
|
|
61
|
+
function installRtkBinary(ctx) {
|
|
62
|
+
// Method 1: official prebuilt install script (pinned URL).
|
|
63
|
+
ctx.ui.info(ctx.t('rtkInstalling', { method: `prebuilt script (${RTK_PREBUILT_INSTALL_URL})` }));
|
|
64
|
+
const prebuilt = run('sh', ['-c', `curl -fsSL ${RTK_PREBUILT_INSTALL_URL} | sh`], {
|
|
65
|
+
timeout: RTK_PREBUILT_TIMEOUT_MS
|
|
66
|
+
});
|
|
67
|
+
if (prebuilt.ok) return true;
|
|
68
|
+
|
|
69
|
+
// Method 2: `cargo install rtk` if cargo is on PATH.
|
|
70
|
+
if (hasCmd('cargo')) {
|
|
71
|
+
ctx.ui.info(ctx.t('rtkInstalling', { method: 'cargo install rtk' }));
|
|
72
|
+
const cargo = run('cargo', ['install', 'rtk']);
|
|
73
|
+
if (cargo.ok) return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// No method succeeded.
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Phase entrypoint. R2-01 wires the full install + hook + manifest pipeline.
|
|
82
|
+
* The consent gate is delegated to `shouldRunRtk(ctx)` (R1-01).
|
|
83
|
+
*
|
|
84
|
+
* @param {object} ctx
|
|
85
|
+
* @returns {Promise<object>} the same ctx
|
|
86
|
+
*/
|
|
87
|
+
async function setupRtk(ctx) {
|
|
88
|
+
if (!(await shouldRunRtk(ctx))) {
|
|
89
|
+
// Dry-run: enumerate the action set that would have run (validated 2026-06-15).
|
|
90
|
+
if (ctx.dryRun) {
|
|
91
|
+
ctx.ui.info(
|
|
92
|
+
'[dry-run] rtk setup skipped (would: detect rtk binary, install if missing via prebuilt/cargo, run rtk init -g, check jq)'
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
ctx.ui.info(ctx.t('rtkSkipped'));
|
|
96
|
+
}
|
|
97
|
+
return ctx;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Step 1: ensure the binary is on PATH (R2.1, R2.2, R2.3).
|
|
102
|
+
const state = ensureRtkBinary(ctx);
|
|
103
|
+
if (state === 'absent') {
|
|
104
|
+
ctx.ui.warn(ctx.t('rtkInstallFailed'));
|
|
105
|
+
return ctx;
|
|
106
|
+
}
|
|
107
|
+
if (state === 'installed') {
|
|
108
|
+
ctx.ui.info(ctx.t('rtkInstalled'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Step 2: register rtk's official Claude Code hook via `rtk init -g` (R2.4).
|
|
112
|
+
const init = run('rtk', ['init', '-g'], { timeout: RTK_INIT_TIMEOUT_MS });
|
|
113
|
+
if (!init.ok) {
|
|
114
|
+
ctx.ui.warn(ctx.t('rtkInitFailed', { reason: init.stderr || `exit ${init.status}` }));
|
|
115
|
+
return ctx;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Step 3: `jq` precheck (R2.5). Warn if missing; do not fail.
|
|
119
|
+
if (!hasCmd('jq')) {
|
|
120
|
+
ctx.ui.warn(ctx.t('rtkNeedsJq'));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Step 4: record the action (R2.6, idempotency). In-memory flag — re-runs
|
|
124
|
+
// observe `hasCmd('rtk')` short-circuit and re-invoke `rtk init -g`,
|
|
125
|
+
// which is itself idempotent. R3-01 reads this flag for the summary line.
|
|
126
|
+
ctx.rtkSetupRan = true;
|
|
127
|
+
return ctx;
|
|
128
|
+
} catch (err) {
|
|
129
|
+
// Non-fatal boundary: any unexpected throw becomes a warning, the
|
|
130
|
+
// installer continues (R2.7, R4.1).
|
|
131
|
+
ctx.ui.warn(ctx.t('rtkFailed', { reason: (err && err.message) || String(err) }));
|
|
132
|
+
return ctx;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Decision matrix for whether the rtk setup phase should run. R1-01.
|
|
138
|
+
*
|
|
139
|
+
* 1. --dry-run → false (R1.4)
|
|
140
|
+
* 2. ctx.options.withRtk === true → true (R1.1)
|
|
141
|
+
* 3. !ctx.interactive → false (R1.3)
|
|
142
|
+
* 4. interactive confirm resolves true → true (R1.2)
|
|
143
|
+
* 5. interactive confirm resolves false → false (R1.2)
|
|
144
|
+
*
|
|
145
|
+
* @param {object} ctx
|
|
146
|
+
* @returns {Promise<boolean>}
|
|
147
|
+
*/
|
|
148
|
+
async function shouldRunRtk(ctx) {
|
|
149
|
+
if (ctx.dryRun) return false;
|
|
150
|
+
if (ctx.options.withRtk) return true;
|
|
151
|
+
if (!ctx.interactive) return false;
|
|
152
|
+
const yes = await ctx.ui.confirm(
|
|
153
|
+
{ message: ctx.t('rtkConfirm'), initialValue: false },
|
|
154
|
+
false
|
|
155
|
+
);
|
|
156
|
+
return yes === true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = { shouldRunRtk, setupRtk, ensureRtkBinary, installRtkBinary };
|
package/bin/phases/summary.js
CHANGED
|
@@ -55,6 +55,10 @@ function printSummary(ctx) {
|
|
|
55
55
|
for (const key of platforms) {
|
|
56
56
|
lines.push(key === 'claude' ? t('nsClaude') : t('nsOpencode'));
|
|
57
57
|
}
|
|
58
|
+
// rtk token-saver result (set by phases/setup-rtk.js when setup completed).
|
|
59
|
+
if (ctx.rtkSetupRan) {
|
|
60
|
+
lines.push(t('rtkSummary'));
|
|
61
|
+
}
|
|
58
62
|
const keySkills = skillsNeedingKeys(platforms);
|
|
59
63
|
if (keySkills.length > 0) {
|
|
60
64
|
lines.push(t('nsKeys', { skills: keySkills.join(', ') }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|