throughline 0.3.18 → 0.3.20
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 +15 -8
- package/package.json +1 -1
- package/src/token-monitor.mjs +2 -2
- package/src/token-monitor.test.mjs +2 -2
- package/src/vscode-task.mjs +33 -0
- package/src/vscode-task.test.mjs +58 -0
package/README.md
CHANGED
|
@@ -205,24 +205,31 @@ Example output (real values from a running 1M-context Opus session):
|
|
|
205
205
|
### VS Code auto-start (automatic)
|
|
206
206
|
|
|
207
207
|
After `throughline install`, any VS Code / Cursor / VSCodium project you work in
|
|
208
|
-
gets `.vscode/tasks.json` provisioned automatically on the
|
|
208
|
+
gets `.vscode/tasks.json` provisioned automatically on the first session event.
|
|
209
209
|
The file configures `runOn: folderOpen` so the monitor appears in a dedicated
|
|
210
210
|
terminal panel the next time you open that folder.
|
|
211
211
|
|
|
212
|
-
**How it works.**
|
|
212
|
+
**How it works.** `ensureMonitorTaskFile` is called from **all three hooks
|
|
213
|
+
(SessionStart, UserPromptSubmit, Stop)** as of v0.3.18. Whichever one fires
|
|
214
|
+
first in your environment creates the file; the rest are idempotent no-ops.
|
|
213
215
|
Once per project it inspects `.vscode/tasks.json`:
|
|
214
216
|
|
|
215
|
-
- **No file yet** → creates one with a single `Throughline Monitor` task
|
|
217
|
+
- **No file yet** → creates one with a single `Throughline Monitor` task, and
|
|
218
|
+
emits a one-time `<system-reminder>` to stdout so Claude tells you a
|
|
219
|
+
**Developer: Reload Window** is needed to activate the `folderOpen` task once
|
|
220
|
+
(v0.3.19+).
|
|
216
221
|
- **Plain JSON with other tasks** → appends the monitor task, preserves your
|
|
217
|
-
existing entries, `version`, and indentation.
|
|
222
|
+
existing entries, `version`, and indentation (same notice fires once).
|
|
218
223
|
- **JSONC (comments or trailing commas)** → does not touch the file. Prints a
|
|
219
224
|
one-time notice to stderr asking you to paste the snippet below.
|
|
220
225
|
- **Already contains a Throughline Monitor task** → does nothing (idempotent;
|
|
221
|
-
this is the common path on every subsequent turn).
|
|
226
|
+
this is the common path on every subsequent turn; notice is silent).
|
|
222
227
|
|
|
223
|
-
The generated task uses `type: '
|
|
224
|
-
`bin/throughline.mjs
|
|
225
|
-
|
|
228
|
+
The generated task uses `type: 'shell'` with the absolute path to Node and
|
|
229
|
+
`bin/throughline.mjs`. VS Code wraps shell tasks in a PTY (xterm.js) so the
|
|
230
|
+
monitor sees `isTTY=true`, real `columns`, and resize events. Windows `.cmd`
|
|
231
|
+
shims and missing PATH entries cannot break it because the command is already
|
|
232
|
+
an absolute Node binary path.
|
|
226
233
|
|
|
227
234
|
**Opt out:** set `THROUGHLINE_NO_VSCODE=1` in the environment used by Claude
|
|
228
235
|
Code. Delete `.vscode/tasks.json` (or just the monitor entry) if you want to
|
package/package.json
CHANGED
package/src/token-monitor.mjs
CHANGED
|
@@ -304,8 +304,8 @@ function formatLine({ state, usage, isActive, now = Date.now() }) {
|
|
|
304
304
|
// 90%超: !! + 強い文言 (赤)
|
|
305
305
|
// 70%超: ! + 弱い文言 (黄)
|
|
306
306
|
const warn =
|
|
307
|
-
ratio >= 0.9 ? color(ANSI.red + ANSI.bold, ' !! /
|
|
308
|
-
ratio >= 0.7 ? color(ANSI.yellow, ' ! そろそろ /
|
|
307
|
+
ratio >= 0.9 ? color(ANSI.red + ANSI.bold, ' !! /tl 強く推奨') :
|
|
308
|
+
ratio >= 0.7 ? color(ANSI.yellow, ' ! そろそろ /tl') :
|
|
309
309
|
'';
|
|
310
310
|
|
|
311
311
|
const marker = isActive ? color(ANSI.bold + ANSI.cyan, '▶') : ' ';
|
|
@@ -305,13 +305,13 @@ test('formatLine: 70% 未満は警告テキストなし', () => {
|
|
|
305
305
|
const out = stripColors(formatLine(makeLineArgs(0.5)));
|
|
306
306
|
assert.ok(!out.includes('!!'));
|
|
307
307
|
assert.ok(!out.includes('! '));
|
|
308
|
-
assert.ok(!out.includes('/
|
|
308
|
+
assert.ok(!out.includes('/tl'));
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
test('formatLine: 70% 以上で "!" マーカーと弱めの文言', () => {
|
|
312
312
|
const out = stripColors(formatLine(makeLineArgs(0.75)));
|
|
313
313
|
assert.ok(out.includes('!'), 'should include ! marker');
|
|
314
|
-
assert.ok(out.includes('そろそろ /
|
|
314
|
+
assert.ok(out.includes('そろそろ /tl'), 'should show soft warning');
|
|
315
315
|
assert.ok(!out.includes('!!'), 'should not include critical marker yet');
|
|
316
316
|
});
|
|
317
317
|
|
package/src/vscode-task.mjs
CHANGED
|
@@ -161,6 +161,37 @@ function atomicWrite(targetPath, contents) {
|
|
|
161
161
|
renameSync(tmp, targetPath);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* tasks.json を「今まさに作成/マージした」瞬間にユーザー向けへ送る通知テキスト。
|
|
166
|
+
*
|
|
167
|
+
* SessionStart / UserPromptSubmit の stdout は Claude Code に additional context として
|
|
168
|
+
* 取り込まれる。<system-reminder> で包んで Claude に「これをユーザーに伝える」ことを
|
|
169
|
+
* 明示しておくことで、新規プロジェクト初回セットアップ直後にユーザーへ
|
|
170
|
+
* 「Reload Window が必要」を即時通知できる。
|
|
171
|
+
*
|
|
172
|
+
* 通知するのは action が 'created' か 'merged' の場合のみ。既に存在した場合は
|
|
173
|
+
* セットアップ済みのため沈黙する(冪等性)。
|
|
174
|
+
*/
|
|
175
|
+
export function buildSetupNotice(action) {
|
|
176
|
+
if (action !== 'created' && action !== 'merged') return null;
|
|
177
|
+
return (
|
|
178
|
+
'<system-reminder>\n' +
|
|
179
|
+
'Throughline: このプロジェクトに token-monitor の自動起動設定を追加しました (.vscode/tasks.json)。\n' +
|
|
180
|
+
'今すぐ monitor を起動するには、VSCode のコマンドパレット (Ctrl+Shift+P) から\n' +
|
|
181
|
+
' Developer: Reload Window\n' +
|
|
182
|
+
'を実行してください。次回以降はフォルダを開いた瞬間に自動起動します。\n' +
|
|
183
|
+
'\n' +
|
|
184
|
+
'このお知らせは今このプロジェクトで初回セットアップが完了したためだけに 1 度だけ流れています。\n' +
|
|
185
|
+
'必ずユーザー(あなたの対話相手)に日本語でそのまま伝えてください。\n' +
|
|
186
|
+
'</system-reminder>\n'
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function emitSetupNotice(action) {
|
|
191
|
+
const text = buildSetupNotice(action);
|
|
192
|
+
if (text) process.stdout.write(text);
|
|
193
|
+
}
|
|
194
|
+
|
|
164
195
|
function emitJsoncGuidanceOnce(vscodeDir) {
|
|
165
196
|
const markerPath = join(vscodeDir, JSONC_MARKER_FILENAME);
|
|
166
197
|
if (existsSync(markerPath)) return;
|
|
@@ -215,6 +246,7 @@ export function ensureMonitorTaskFile(opts = {}) {
|
|
|
215
246
|
if (!existsSync(vscodeDir)) mkdirSync(vscodeDir, { recursive: true });
|
|
216
247
|
const obj = { version: '2.0.0', tasks: [buildMonitorTask(bin)] };
|
|
217
248
|
atomicWrite(tasksPath, JSON.stringify(obj, null, 2) + '\n');
|
|
249
|
+
emitSetupNotice('created');
|
|
218
250
|
return { action: 'created', path: tasksPath };
|
|
219
251
|
}
|
|
220
252
|
|
|
@@ -243,5 +275,6 @@ export function ensureMonitorTaskFile(opts = {}) {
|
|
|
243
275
|
tasks: [...(Array.isArray(obj.tasks) ? obj.tasks : []), buildMonitorTask(bin)],
|
|
244
276
|
};
|
|
245
277
|
atomicWrite(tasksPath, JSON.stringify(nextObj, null, indent) + '\n');
|
|
278
|
+
emitSetupNotice('merged');
|
|
246
279
|
return { action: 'merged', path: tasksPath };
|
|
247
280
|
}
|
package/src/vscode-task.test.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
detectIndent,
|
|
11
11
|
hasMonitorTask,
|
|
12
12
|
buildMonitorTask,
|
|
13
|
+
buildSetupNotice,
|
|
13
14
|
} from './vscode-task.mjs';
|
|
14
15
|
|
|
15
16
|
const VSCODE_ENV = { TERM_PROGRAM: 'vscode' };
|
|
@@ -518,3 +519,60 @@ test('ensureMonitorTaskFile: parse_error for malformed JSON', () => {
|
|
|
518
519
|
cleanup();
|
|
519
520
|
}
|
|
520
521
|
});
|
|
522
|
+
|
|
523
|
+
// --- buildSetupNotice ---
|
|
524
|
+
|
|
525
|
+
test('buildSetupNotice: returns notice text for created', () => {
|
|
526
|
+
const text = buildSetupNotice('created');
|
|
527
|
+
assert.ok(text && text.includes('<system-reminder>'));
|
|
528
|
+
assert.ok(text.includes('Reload Window'));
|
|
529
|
+
assert.ok(text.includes('tasks.json'));
|
|
530
|
+
assert.ok(text.includes('ユーザー'));
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
test('buildSetupNotice: returns notice text for merged', () => {
|
|
534
|
+
const text = buildSetupNotice('merged');
|
|
535
|
+
assert.ok(text && text.includes('<system-reminder>'));
|
|
536
|
+
assert.ok(text.includes('Reload Window'));
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test('buildSetupNotice: returns null for already_present (silent idempotency)', () => {
|
|
540
|
+
assert.equal(buildSetupNotice('already_present'), null);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test('buildSetupNotice: returns null for skipped', () => {
|
|
544
|
+
assert.equal(buildSetupNotice('skipped'), null);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('buildSetupNotice: ensureMonitorTaskFile writes notice to stdout on first creation', () => {
|
|
548
|
+
const { dir, cleanup } = mkTmpCwd();
|
|
549
|
+
const captured = [];
|
|
550
|
+
const origWrite = process.stdout.write.bind(process.stdout);
|
|
551
|
+
process.stdout.write = (chunk) => {
|
|
552
|
+
captured.push(typeof chunk === 'string' ? chunk : chunk.toString('utf8'));
|
|
553
|
+
return true;
|
|
554
|
+
};
|
|
555
|
+
try {
|
|
556
|
+
const r1 = ensureMonitorTaskFile({
|
|
557
|
+
cwd: dir,
|
|
558
|
+
env: VSCODE_ENV,
|
|
559
|
+
throughlineBin: FAKE_BIN,
|
|
560
|
+
});
|
|
561
|
+
assert.equal(r1.action, 'created');
|
|
562
|
+
const r2 = ensureMonitorTaskFile({
|
|
563
|
+
cwd: dir,
|
|
564
|
+
env: VSCODE_ENV,
|
|
565
|
+
throughlineBin: FAKE_BIN,
|
|
566
|
+
});
|
|
567
|
+
assert.equal(r2.action, 'already_present');
|
|
568
|
+
} finally {
|
|
569
|
+
process.stdout.write = origWrite;
|
|
570
|
+
cleanup();
|
|
571
|
+
}
|
|
572
|
+
const joined = captured.join('');
|
|
573
|
+
assert.ok(joined.includes('<system-reminder>'), 'notice should be written on created');
|
|
574
|
+
assert.ok(joined.includes('Reload Window'));
|
|
575
|
+
// 2 回目 (already_present) では notice は出ない = created 分の 1 回のみ
|
|
576
|
+
const count = (joined.match(/<system-reminder>/g) ?? []).length;
|
|
577
|
+
assert.equal(count, 1, 'notice should be emitted exactly once (idempotency)');
|
|
578
|
+
});
|