@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 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);
@@ -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 };
@@ -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.11.11",
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",