sprag-cli 3.70.0 → 3.71.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.
Files changed (45) hide show
  1. package/README.ko.md +7 -1
  2. package/README.md +7 -1
  3. package/bin/cli.js +47 -5
  4. package/docs/CODEX.ko.md +2 -2
  5. package/docs/CODEX.md +4 -4
  6. package/docs/TEAM.ko.md +74 -19
  7. package/docs/TEAM.md +75 -19
  8. package/package.json +2 -1
  9. package/presets/agents.json +118 -0
  10. package/presets/doc2md/convert.py +1 -1
  11. package/presets/ratchet-rules.json +12 -2
  12. package/src/cli-entry.js +40 -0
  13. package/src/codex/hooks.js +11 -10
  14. package/src/codex/panel-setup.js +2 -1
  15. package/src/codex/panel.js +2 -1
  16. package/src/commands/brief.js +2 -0
  17. package/src/commands/codex.js +3 -2
  18. package/src/commands/harness.js +2 -3
  19. package/src/commands/install.js +19 -18
  20. package/src/commands/presets.js +158 -0
  21. package/src/commands/profile-map.js +2 -2
  22. package/src/commands/route-scan.js +9 -7
  23. package/src/commands/team.js +82 -26
  24. package/src/commands/update-check.js +2 -2
  25. package/src/debug.js +3 -2
  26. package/src/delegation-agents.js +48 -60
  27. package/src/doc2md.cjs +4 -3
  28. package/src/env.cjs +7 -0
  29. package/src/env.js +19 -0
  30. package/src/first-run-note.js +3 -2
  31. package/src/harness.js +4 -14
  32. package/src/hook.cjs +4 -3
  33. package/src/preset-sign.js +106 -0
  34. package/src/preset-sync.js +133 -0
  35. package/src/presets-source.js +209 -0
  36. package/src/prompt.js +2 -1
  37. package/src/route-scan.js +14 -1
  38. package/src/seed-rules.js +14 -16
  39. package/src/star-note.js +3 -2
  40. package/src/statusline-guard.js +144 -0
  41. package/src/stdin-payload.js +33 -4
  42. package/src/team-prompts.js +353 -0
  43. package/src/team-ratchet.js +116 -16
  44. package/src/team-sync.js +336 -37
  45. package/src/update-check.js +3 -2
package/README.ko.md CHANGED
@@ -192,6 +192,12 @@ Codex에는 기본 훅과 `AGENTS.md` 하네스, 범위별 래칫 규칙, 한국
192
192
 
193
193
  전체 명령 목록과 Claude Code·Codex 비교, 집계 한계, 제거 방법은 [Codex 지원 범위와 제한](https://sprag.io/docs/ko/codex/)([원문](https://github.com/rootstudioyaml/sprag/blob/main/docs/CODEX.ko.md))에 있습니다. `sprag capabilities --agent codex`로 기능별 지원 여부를 확인할 수도 있습니다.
194
194
 
195
+ ## 프리셋
196
+
197
+ `presets/` 에 들어 있는 래칫 프리셋, 모델 라우팅 프리셋, 위임 에이전트는 Sprag 콘솔에서도 게시합니다. CLI는 하루에 한 번 Sprag 서버에 새 버전이 있는지 묻고, 서명이 CLI에 들어 있는 키로 확인될 때만 그 버전을 적용합니다. 이 요청은 읽기 전용이고, 사용자를 알아볼 만한 정보는 담지 않습니다. 오프라인이거나 막혀 있거나 서명이 맞지 않으면 패키지에 동봉된 사본을 계속 씁니다. 새 버전은 다음 세션을 시작할 때 적용되고, `sprag seed` 로 이미 수락한 프리셋은 고쳐진 문구를 알아서 받습니다. 새로 생긴 프리셋은 지금처럼 하나씩 제안합니다.
198
+
199
+ `sprag presets status` 는 어느 사본을 왜 쓰는지 보여 주고, `sprag presets sync` 는 바로 확인하고, `sprag presets show` 는 지금 적용 중인 프리셋을 출력합니다. 받아 오기를 끄려면 `SPRAG_NO_PRESET_SYNC=1` 을 설정하십시오. 이 요청은 [설치 안내](https://github.com/rootstudioyaml/sprag/blob/main/docs/INSTALL.ko.md)의 네트워크 표에 있습니다.
200
+
195
201
  ## 자주 쓰는 명령
196
202
 
197
203
  | 명령 | 하는 일 |
@@ -254,7 +260,7 @@ sprag feedback "Bedrock에서 5m TTL 칩이 사라지지 않아요"
254
260
 
255
261
  Sprag 은 켜지 않으면 로컬에서만 동작합니다. `sprag team join <토큰>` 을 실행하거나(조직 전체는 `SPRAG_TEAM_TOKEN`) 하면 컴퓨터마다 하루에 한 행씩 올라갑니다. 내용은 지출, 절감액, 위임 실행 횟수, 모델 계열 비중입니다. 프롬프트, 대화 기록, 파일 경로, 프로젝트 이름은 보내지 않으며, `sprag team status --show-payload` 로 올라갈 내용을 그대로 볼 수 있습니다. 그 대신 팀 소유자는 대시보드에서 구성원별 합계를 보고, 모든 구성원에게 자동으로 적용될 래칫 룰을 승인할 수 있습니다.
256
262
 
257
- `CTS_NO_TEAM_SYNC=1` 은 팀 관련 통신을 모두 끄고, `sprag team leave` 는 참여를 해제합니다.
263
+ `SPRAG_NO_TEAM_SYNC=1` 은 팀 관련 통신을 모두 끄고, `sprag team leave` 는 참여를 해제합니다.
258
264
 
259
265
  - [팀 기능 안내](docs/TEAM.ko.md)
260
266
  - 대시보드: https://sprag.io/team/
package/README.md CHANGED
@@ -246,6 +246,12 @@ Commands default to Claude Code; add `--agent codex` to pick Codex, for example
246
246
 
247
247
  The full command list, the Claude Code and Codex comparison, accounting limits and removal are in [Codex support and limits](https://sprag.io/docs/codex/) ([source](https://github.com/rootstudioyaml/sprag/blob/main/docs/CODEX.md)), or run `sprag capabilities --agent codex` for the support matrix.
248
248
 
249
+ ## Presets
250
+
251
+ The ratchet presets, model-routing presets and delegation agents in `presets/` are also published centrally from the Sprag console. Once a day the CLI asks the Sprag server whether a newer version exists, a read-only request that carries nothing about you, and applies it only when its signature verifies against a key built into the CLI. Offline, blocked or unverified, it keeps the copy bundled with the package. A new version takes effect at the next session start, and presets you already accepted with `sprag seed` pick up reworded text on their own; new presets are still offered one at a time.
252
+
253
+ `sprag presets status` shows which copy is in use and why, `sprag presets sync` checks now, `sprag presets show` prints the effective presets, and `SPRAG_NO_PRESET_SYNC=1` turns the fetch off. The request is listed in the network table of the [install guide](https://github.com/rootstudioyaml/sprag/blob/main/docs/INSTALL.md).
254
+
249
255
  ## Day-to-day commands
250
256
 
251
257
  | Command | What it does |
@@ -308,7 +314,7 @@ For a bug, please include the tool version (`sprag --version`) and your OS. For
308
314
 
309
315
  Sprag stays local unless you opt in. With `sprag team join <token>` (or `SPRAG_TEAM_TOKEN` for a whole organization), each machine uploads one row per day: spend, savings, delegated-run count and model-family share. Prompts, transcripts, file paths and project names are never sent, and `sprag team status --show-payload` prints exactly what would go up. In return the team owner sees per-member totals in a dashboard and can approve ratchet rules that every member picks up automatically.
310
316
 
311
- `CTS_NO_TEAM_SYNC=1` turns all team traffic off, and `sprag team leave` removes the membership.
317
+ `SPRAG_NO_TEAM_SYNC=1` turns all team traffic off, and `sprag team leave` removes the membership.
312
318
 
313
319
  - [Team layer guide](docs/TEAM.md)
314
320
  - Dashboard: https://sprag.io/team/
package/bin/cli.js CHANGED
@@ -26,6 +26,7 @@
26
26
  * # (or set CACHE_MONITOR_EXCLUDE_SESSION env var)
27
27
  */
28
28
 
29
+ import { envVar } from '../src/env.js';
29
30
  import { readFileSync } from 'node:fs';
30
31
  import { fileURLToPath } from 'node:url';
31
32
  import { dirname, isAbsolute, join } from 'node:path';
@@ -102,7 +103,7 @@ function segmentsFromArgs() {
102
103
  const KNOWN_SUBCOMMANDS = new Set([
103
104
  'last', 'brief', 'history', 'handoff', 'install', 'uninstall', 'mode', 'korean', 'cohesion',
104
105
  'doc2md', 'delegate', 'harness', 'route-scan', 'compact-window', 'update-check', 'upgrade',
105
- 'seed', 'litellm-budget', 'profile-map', 'feedback', 'saved', 'cowork', 'team',
106
+ 'seed', 'litellm-budget', 'profile-map', 'feedback', 'saved', 'cowork', 'team', 'presets',
106
107
  ]);
107
108
 
108
109
  // Flags each entry path reads, collected from the hasFlag/getArg/numArg calls in
@@ -196,6 +197,7 @@ Usage:
196
197
  sprag handoff write a session handoff file
197
198
  sprag mode [keywords] statusline preferences (icon/text, en/ko, window, ttl)
198
199
  sprag seed register the bundled starter rules, one answer at a time
200
+ sprag presets [status|sync|show] centrally published presets: source, fetch now, effective list
199
201
  sprag cowork [sync|on|off|exclude <dir>|include <dir>] copy the ratchet rules into Cowork tab folders
200
202
  sprag korean on|off|status Korean writing guidance + write-time check
201
203
  sprag cohesion on|off|status English sentence-connection guidance
@@ -207,7 +209,7 @@ Usage:
207
209
  sprag update-check is a newer version out?
208
210
  sprag brief --hook internal: the UserPromptSubmit hook entry point
209
211
  sprag feedback "<msg>" file a bug report / feature request (no browser needed)
210
- sprag team create|join|status|sync|leave opt-in team layer: daily aggregates only (CTS_NO_TEAM_SYNC=1 turns it off)
212
+ sprag team create|join|status|sync|leave opt-in team layer: daily aggregates only (SPRAG_NO_TEAM_SYNC=1 turns it off)
211
213
  sprag upgrade install the latest release
212
214
  sprag --install-hook install cache-monitor PostToolUse hook
213
215
  sprag --uninstall-hook remove that hook
@@ -395,6 +397,13 @@ async function main() {
395
397
  return (await import('../src/commands/seed.js')).run({ args, hasFlag });
396
398
  }
397
399
 
400
+ // Subcommand: presets — the centrally published preset bundle (read-only
401
+ // fetch, signature-checked) or the bundled fallback. See src/presets-source.js.
402
+ // sprag presets [status] | presets sync [--force] | presets show [--json]
403
+ if (args[0] === 'presets') {
404
+ return (await import('../src/commands/presets.js')).run({ args, hasFlag });
405
+ }
406
+
398
407
  // Subcommand: cowork — ratchet copies for the Claude app's Cowork tab.
399
408
  if (args[0] === 'cowork') {
400
409
  return (await import('../src/commands/cowork.js')).run({ args, hasFlag });
@@ -561,6 +570,36 @@ async function main() {
561
570
  return;
562
571
  }
563
572
 
573
+ // Overlap guard: Claude Code relaunches the statusline every 5s without
574
+ // waiting for the previous run, so a slow render must not pile up processes.
575
+ let guard = null;
576
+ if (isStatusline && !demoArg) {
577
+ const g = await import('../src/statusline-guard.js');
578
+ const key = g.sessionKeyFrom(readStdinJson());
579
+ const lock = g.acquireStatuslineLock({ key });
580
+ if (!lock.acquired) {
581
+ const last = g.readLastRender({ key });
582
+ if (last !== null) console.log(last);
583
+ return;
584
+ }
585
+ const timer = g.armStatuslineDeadline({
586
+ budgetMs: g.statuslineBudgetMs(),
587
+ onExpire: () => {
588
+ const last = g.readLastRender({ key });
589
+ if (last !== null) console.log(last);
590
+ lock.release();
591
+ process.exit(0);
592
+ },
593
+ });
594
+ guard = {
595
+ finish(text) {
596
+ g.writeLastRender({ key, text });
597
+ if (timer) clearTimeout(timer);
598
+ lock.release();
599
+ },
600
+ };
601
+ }
602
+
564
603
  // Report generation
565
604
  // Statusline window comes from persisted config — hours-precise so users
566
605
  // can pick `1h` / `6h` etc, not just whole days. Other formats default to
@@ -642,10 +681,12 @@ async function main() {
642
681
  } catch (e) { debug('caps-cache:persist', e); }
643
682
  }
644
683
  const { withBaseStatusline } = await import('../src/statusline-base.js');
645
- console.log(withBaseStatusline(formatNoSession(
684
+ const noSessionOut = withBaseStatusline(formatNoSession(
646
685
  { caps, model, effort, windowLabel, version: PKG_VERSION, update: readUpdateChip(PKG_VERSION) },
647
686
  { color: colorOk, mode: labelMode, segments: segmentsFromArgs() },
648
- ), { input: readStdinRaw() }));
687
+ ), { input: readStdinRaw() });
688
+ if (guard) guard.finish(noSessionOut);
689
+ console.log(noSessionOut);
649
690
  return;
650
691
  }
651
692
  // JSON mode stays JSON. A caller that asked for machine-readable output
@@ -1015,7 +1056,7 @@ async function main() {
1015
1056
  // could take. The scan is local and writes only sprag's own cache file;
1016
1057
  // --project skips it because the scan covers all projects, not one.
1017
1058
  let routing = null;
1018
- if (!projectFilter && process.env.CTS_NO_ROUTE_SCAN !== '1') {
1059
+ if (!projectFilter && envVar('NO_ROUTE_SCAN') !== '1') {
1019
1060
  try {
1020
1061
  const rs = await import('../src/route-scan.js');
1021
1062
  let cache = rs.readRouteScan();
@@ -1040,6 +1081,7 @@ async function main() {
1040
1081
  const { withBaseStatusline } = await import('../src/statusline-base.js');
1041
1082
  // The payload was read above; the base gets the same text, not a second read.
1042
1083
  output = withBaseStatusline(output, { input: readStdinRaw() });
1084
+ if (guard) guard.finish(output);
1043
1085
  }
1044
1086
  console.log(output);
1045
1087
  }
package/docs/CODEX.ko.md CHANGED
@@ -30,7 +30,7 @@ sprag install --agent codex
30
30
  Claude 설정이나 `config.toml`은 고치지 않습니다.
31
31
 
32
32
  macOS zsh 환경에서는 설치가 tmux 설치 여부를 먼저 확인합니다. tmux가 없으면 Homebrew로 설치하고,
33
- `CTS_NO_TMUX=1`을 지정하면 이 단계를 건너뜁니다. 이어서 zsh 연동을 추가해 `codex`를 입력하면 같은
33
+ `SPRAG_NO_TMUX=1`을 지정하면 이 단계를 건너뜁니다. 이어서 zsh 연동을 추가해 `codex`를 입력하면 같은
34
34
  터미널 안에 패널이 인라인으로 붙으며, 이때는 별도 창을 여는 훅을 등록하지 않습니다. tmux를 설치할 수
35
35
  없거나 로그인 셸이 zsh가 아니거나 이미 사용자의 `codex` 별칭·함수가 있으면 별도 Terminal 창 방식으로
36
36
  대신 동작합니다. `sprag panel shell remove --agent codex`로 끈 설정은 재설치와 업그레이드에도 그대로
@@ -344,7 +344,7 @@ sprag capabilities --agent codex
344
344
  패널은 새 버전이 있으면 사용량 앞에 알림을 표시하고, 백그라운드에서 버전 캐시를 갱신합니다.
345
345
  일회성 상태줄은 캐시만 읽습니다. 대화형 SessionStart는 변경 내용을 알리고 설치 동의를 구합니다.
346
346
  `sprag update-check --dismiss --agent codex`로 해당 버전의 알림을 끌 수 있습니다.
347
- `CTS_NO_UPDATE_CHECK=1` 또는 `NO_UPDATE_NOTIFIER`는 확인 자체를 끕니다.
347
+ `SPRAG_NO_UPDATE_CHECK=1` 또는 `NO_UPDATE_NOTIFIER`는 확인 자체를 끕니다.
348
348
  compaction, 하위 세션 시작, `codex exec`에는 업그레이드 질문을 넣지 않습니다.
349
349
 
350
350
  ```sh
package/docs/CODEX.md CHANGED
@@ -36,7 +36,7 @@ events documented below. Admin policies or `features.hooks = false` may disable
36
36
  hooks. The `sprag` executable and Node.js must be on Codex's PATH.
37
37
 
38
38
  On macOS with zsh, installation also makes sure tmux is present: it installs
39
- tmux with Homebrew when missing (skip this with `CTS_NO_TMUX=1`) and adds the
39
+ tmux with Homebrew when missing (skip this with `SPRAG_NO_TMUX=1`) and adds the
40
40
  zsh integration so typing `codex` shows the panel inline in the same terminal.
41
41
  The separate-window hook is then not registered. When tmux cannot be
42
42
  installed, the login shell is not zsh, or an existing `codex` alias or
@@ -49,7 +49,7 @@ Codex, run `sprag panel doctor --agent codex` to check the last hook execution,
49
49
  any launch failure, and recent rendered frames. These diagnostics are local;
50
50
  they do not read or change Codex's trust database.
51
51
 
52
- `CTS_NO_HARNESS=1` skips the global harness. Korean guidance is opt-in, using the
52
+ `SPRAG_NO_HARNESS=1` skips the global harness. Korean guidance is opt-in, using the
53
53
  existing shared Sprag preference:
54
54
 
55
55
  ```sh
@@ -133,7 +133,7 @@ benchmark.
133
133
 
134
134
  The document converter is shared with Claude Code and requires its optional
135
135
  converter dependencies. Disable automatic Codex conversion and write guards with
136
- `sprag doc2md off --agent codex` or `CTS_NO_DOC2MD=1`. PreToolUse handles supported
136
+ `sprag doc2md off --agent codex` or `SPRAG_NO_DOC2MD=1`. PreToolUse handles supported
137
137
  file-read calls and literal single-file `cat`, `head`, and `tail` reads. Successful
138
138
  conversion blocks the original read with the Markdown path for a retry. Conversion
139
139
  failures are reported; unsafe archives are denied. Patches and recognized shell
@@ -755,7 +755,7 @@ should keep using `npm i -g sprag-cli --ignore-scripts` followed by
755
755
 
756
756
  The panel places an available update before usage metrics. Dismissing a version
757
757
  with `sprag update-check --dismiss --agent codex` hides its chip and session
758
- offer until a newer version appears. `CTS_NO_UPDATE_CHECK=1` or
758
+ offer until a newer version appears. `SPRAG_NO_UPDATE_CHECK=1` or
759
759
  `NO_UPDATE_NOTIFIER` disables checks. Compaction, subagent starts and `codex exec`
760
760
  do not ask to upgrade. SessionStart checks at most every three hours; the live
761
761
  panel uses the shared 24-hour interval and never waits for a network response.
package/docs/TEAM.ko.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [← README](../README.ko.md) · [English](./TEAM.md)
4
4
 
5
- Sprag 은 기본적으로 로컬에서만 동작합니다. 팀 기능은 켜야만 쓰이는 선택 기능입니다. 팀원마다 하루 지출과 절감액을 한 대시보드에서 보고, 팀 소유자가 승인한 래칫 룰을 함께 쓸 수 있습니다. `sprag team join <토큰>` 을 실행하거나 환경에 `SPRAG_TEAM_TOKEN` 을 지정하기 전에는 아무것도 전송하지 않습니다. `CTS_NO_TEAM_SYNC=1` 을 지정하면 다른 설정과 상관없이 팀 관련 네트워크 통신이 모두 꺼집니다.
5
+ Sprag 은 기본적으로 로컬에서만 동작합니다. 팀 기능은 켜야만 쓰이는 선택 기능입니다. 팀원마다 하루 지출과 절감액을 한 대시보드에서 보고, 오너나 어드민이 승인한 래칫 룰을 함께 쓸 수 있습니다. `sprag team join <토큰>` 을 실행하거나 환경에 `SPRAG_TEAM_TOKEN` 을 지정하기 전에는 아무것도 전송하지 않습니다. `SPRAG_NO_TEAM_SYNC=1` 을 지정하면 다른 설정과 상관없이 사용량 업로드가 모두 멈춥니다.
6
6
 
7
7
  ## 보내는 것과 보내지 않는 것
8
8
 
@@ -17,12 +17,13 @@ Sprag 은 기본적으로 로컬에서만 동작합니다. 팀 기능은 켜야
17
17
  | Sprag 버전 | 설치된 버전 |
18
18
  | 에이전트 | `claude` 또는 `codex` |
19
19
  | 표시 이름 | 참여할 때 정한 이름이며, 기본값은 OS 사용자 이름 |
20
+ | 운영체제 | OS 이름과 버전(예: macOS 26, Windows 11, Ubuntu 24.04), CPU 아키텍처(arm64, x64)입니다. 참여한 직후 한 번 보내고, 바뀔 때만 다시 보냅니다. 호스트 이름, 사용자 이름, 경로처럼 컴퓨터를 이름으로 알아볼 수 있는 값은 보내지 않습니다 |
20
21
 
21
- 보내지 않는 것은 프롬프트, 대화 기록, 파일 경로, 프로젝트 이름, 룰 내용, API 키입니다. 룰은 직접 `sprag team propose` 를 실행할 때만 밖으로 나갑니다.
22
+ 보내지 않는 것은 프롬프트, 대화 기록, 파일 경로, 프로젝트 이름, 룰 내용, API 키입니다. 프롬프트 수집을 켠 엔터프라이즈 팀의 옵트인 프롬프트 업로드만 예외이며 "프롬프트 사용량 (엔터프라이즈)" 절에 적었습니다. 룰은 직접 `sprag team propose` 를 실행할 때만 밖으로 나갑니다.
22
23
 
23
- 업로드는 6시간에 한 번 이하로 실행됩니다. SessionStart 훅이 띄운 분리된 자식 프로세스가 보내며, statusline 을 그리는 경로에서는 절대 실행되지 않습니다.
24
+ 하루 단위 집계 업로드는 마지막 성공 이후 6시간에 한 번 이하로 실행됩니다. SessionStart 훅(그리고 작은 로컬 파일만 읽는 프롬프트 훅)이 띄운 분리된 자식 프로세스가 보냅니다. 네트워크가 끊기는 등 동기화가 실패하면 6시간을 기다리지 않고 15분, 30분, 60분 뒤에 다시 시도합니다(최대 60분 간격). 마지막으로 올린 날짜를 기억해 두었다가 그 전날부터 최대 30일 치를 다시 보내므로, 며칠 꺼져 있던 컴퓨터도 처음 성공하는 동기화에서 빠진 날을 따라잡습니다. 하루 단위 행은 덮어쓰기라 다시 보내도 안전합니다. 프롬프트 수집을 켠 엔터프라이즈 팀의 프롬프트 업로드는 따로 30분 타이머로 움직입니다("프롬프트 사용량 (엔터프라이즈)" 절 참고). statusline 을 그리는 경로에서는 절대 실행되지 않습니다.
24
25
 
25
- 목적지는 `https://hiycfjwswtkixyoqkznm.supabase.co/rest/v1/rpc/*` 입니다(Supabase, ap-northeast-2 리전). 호출하는 RPC 는 `team_join`, `ledger_upsert`, `team_rules_get`, `team_rule_propose`, `team_leave` 입니다.
26
+ 목적지는 `https://hiycfjwswtkixyoqkznm.supabase.co/rest/v1/rpc/*` 입니다(Supabase, ap-northeast-2 리전). 호출하는 RPC 는 `team_join`, `ledger_upsert`, `team_rules_get`, `team_rule_propose`, `team_leave`, `prompt_chunk_put`(엔터프라이즈 프롬프트 수집일 때만), `member_set_sync`(업로드 스위치 상태 호출, "업로드 스위치" 참고) 입니다.
26
27
 
27
28
  ## CLI에서 팀 만들기
28
29
 
@@ -48,7 +49,7 @@ sprag team create --email you@example.com --name "Acme" --code 123456
48
49
 
49
50
  ## 개인으로 참여하기
50
51
 
51
- 팀 소유자에게 초대 토큰을 받은 뒤 실행합니다.
52
+ 오너나 어드민에게 초대 토큰을 받은 뒤 실행합니다.
52
53
 
53
54
  ```bash
54
55
  sprag team join <토큰> --name "미나"
@@ -67,44 +68,97 @@ Sprag 이 올라갈 항목 목록을 보여 주고 확인을 묻습니다. `--na
67
68
 
68
69
  컴퓨터는 다음 세션이 시작될 때 참여합니다. 그다음 첫 세션에는 팀 이름과 두 명령을 알리는 한 줄 안내가 나옵니다. `sprag team status --show-payload` 는 보내는 내용을 보여 주고, `sprag team leave` 는 전송을 멈춥니다.
69
70
 
70
- `leave` 는 환경 변수보다 우선합니다. 팀에서 나온 컴퓨터는 변수가 남아 있어도 저절로 다시 참여하지 않습니다. 소유자가 새 토큰을 발급하고 구성원이 `sprag team join` 을 다시 실행해야 다시 참여합니다.
71
+ `leave` 는 환경 변수보다 우선합니다. 팀에서 나온 컴퓨터는 변수가 남아 있어도 저절로 다시 참여하지 않습니다. 오너나 어드민이 새 토큰을 발급하고 구성원이 `sprag team join` 을 다시 실행해야 다시 참여합니다.
71
72
 
72
73
  ## 팀 룰
73
74
 
74
- 팀 소유자는 대시보드에서 룰을 승인합니다. 구성원도 룰을 제안할 수 있습니다.
75
+ 룰은 두 단계로 나뉩니다. **조직 룰**은 조직 안의 모든 팀에 적용되고, 오너와 어드민이 대시보드의 규칙 탭에서 직접 씁니다. **팀 룰**은 한 팀에만 적용되고, 역시 오너와 어드민이 그 탭에서 승인합니다. 승인된 팀 룰은 "조직 전체에 적용" 버튼 한 번으로 조직 룰로 올릴 수 있습니다. 이때 팀 쪽 사본은 내려가므로 같은 룰이 한 PC에 두 번 들어가지 않습니다.
76
+
77
+ 구성원도 팀 룰을 제안할 수 있습니다.
75
78
 
76
79
  ```bash
77
80
  sprag team propose "커밋 전에 lint 실행" --scope ratchet
78
81
  ```
79
82
 
80
- `--scope` 는 `ratchet`(기본값) 또는 `model` 입니다. 제안은 소유자가 승인할 때까지 대기 상태로 남습니다.
83
+ `--scope` 는 `ratchet`(기본값) 또는 `model` 입니다. 제안은 오너나 어드민이 승인할 때까지 대기 상태로 남습니다.
84
+
85
+ **룰이 도착하는 시점.** 승인된 룰은 각 PC에서 다음 세션을 시작할 때 반영됩니다. PC는 새 룰을 많아야 한 시간에 한 번 확인하고, 사용량은 6시간마다 올립니다. 바로 적용하려면 `sprag team sync --force` 를 실행한 뒤 새 세션을 여십시오. 어느 쪽 동기화든 룰을 받는 즉시 룰 파일을 다시 쓰므로, 다음 세션은 추가 요청 없이 새 룰을 읽습니다. `sprag team sync --rules-only` 는 룰만 받고 아무것도 올리지 않습니다.
81
86
 
82
- 승인된 룰은 다음 동기화 때 내려옵니다. Sprag 은 이를 `~/.claude/ratchet-team.md`(Codex 는 `<CODEX_HOME>/ratchet-team.md`)에 쓰고, 그 파일이 있는 동안 `~/.claude/CLAUDE.md` 의 전역 harness 블록에서 import 합니다. Sprag 이 세션을 시작할 때 파일을 통째로 다시 쓰므로 직접 고치지 마십시오. `sprag team rules` 는 받아 둔 룰을 보여 줍니다. `sprag team leave` 는 멤버 키, 캐시, import 를 지웁니다.
87
+ Sprag 은 룰을 `~/.claude/ratchet-team.md`(Codex 는 `<CODEX_HOME>/ratchet-team.md`)에 씁니다. 조직 룰이 먼저, 팀 룰이 그다음이고, 모델 룰은 각각의 하위 절로 들어갑니다. 그 파일이 있는 동안 `~/.claude/CLAUDE.md` 의 전역 harness 블록에서 import 합니다. Sprag 이 파일을 통째로 다시 쓰므로 직접 고치지 마십시오. `sprag team rules` 는 받아 둔 룰을 `- [org · model] 내용`, `- [team · ratchet] 내용` 형태로 보여 줍니다. `sprag team leave` 는 멤버 키, 캐시, import 를 지웁니다.
83
88
 
84
89
  ## 상태 확인, 전송 내용 확인, 나가기
85
90
 
86
91
  | 명령 | 설명 |
87
92
  |---|---|
88
93
  | `sprag team join [토큰] [--name <이름>] [--yes]` | 팀에 참여 |
89
- | `sprag team status [--show-payload]` | 팀, 플랜, 프롬프트 수집 설정, 표시 이름, 마지막 동기화를 보여 줍니다. `--show-payload` 를 붙이면 올라갈 하루 단위 행도 그대로 출력합니다 |
90
- | `sprag team sync [--force]` | 지금 업로드하고 룰을 받습니다. `--force` 는 6시간 간격을 무시합니다 |
91
- | `sprag team rules` | 받아 둔 팀 룰 보기 |
94
+ | `sprag team status [--show-payload]` | 팀, 플랜, 프롬프트 수집 설정, 표시 이름, 마지막 동기화를 보여 줍니다. `--show-payload` 를 붙이면 보내는 기기 정보와 올라갈 하루 단위 행도 그대로 출력합니다 |
95
+ | `sprag team sync [--force] [--rules-only]` | 지금 업로드하고 룰을 받습니다. `--force` 는 6시간 간격을 무시하고, `--rules-only` 는 조직·팀 룰만 받고 아무것도 올리지 않습니다 |
96
+ | `sprag team rules` | 받아 둔 조직·팀 룰 보기 |
92
97
  | `sprag team propose "<내용>" [--scope ratchet\|model]` | 팀에 룰 제안 |
93
98
  | `sprag team leave` | 팀에서 나가고 업로드 중단 |
94
99
 
100
+ ## 조직과 좌석
101
+
102
+ 모든 팀은 조직에 속하고, 좌석은 팀이 아니라 조직 단위로 구매합니다. 조직을 지정하지 않고 만든 팀에는 조직이 자동으로 새로 생기므로 기존 설정은 그대로 동작합니다.
103
+
104
+ - **좌석이란.** 팀을 떠나지 않았고, 최근 30일 안에 무엇이든 보냈거나 업로드를 멈춘 상태인 컴퓨터 한 대(멤버 키 하나)입니다. 멈추지 않았는데 30일 넘게 연락이 없는 컴퓨터는 좌석을 차지하지 않습니다. 떠난 멤버도 세지 않습니다.
105
+ - **두 번째 컴퓨터.** 같은 사람이 컴퓨터 두 대를 쓰면 오너나 어드민이 두 번째 컴퓨터를 첫 번째에 합쳐 좌석 하나를 같이 쓰게 할 수 있습니다. 대시보드의 멤버 행에서 "좌석 합치기"를 누르고, "합치기 해제"를 누르면 다시 따로 셉니다. 합쳐진 행에는 "OO님과 자리를 같이 씁니다"가 보입니다.
106
+ - **한도.** 조직마다 좌석 한도가 있고, 한도를 넘는 참여는 거절됩니다. `sprag team join` 은 "이 조직에 남은 좌석이 없습니다. 조직 오너나 어드민에게 좌석 추가를 요청한 뒤 다시 참여하세요"를 출력하고 종료 코드 1로 끝납니다. 환경 변수로 하는 자동 참여는 조용히 넘어갔다가 다음 세션 시작 때 다시 시도합니다. 한도를 현재 사용량보다 낮춰도 기존 멤버는 그대로 쓰고, Sprag가 다시 올릴 때까지 새 참여만 거절됩니다.
107
+ - **역할.** 권한 단계는 세 가지입니다. 슈퍼유저는 Sprag 직원이며 요금제와 좌석 한도를 정하고, 조직을 정지하고, 오너를 추가하고, sprag.io/ops/ 에서 모든 조직을 봅니다. 오너는 회사의 최상위 단계로, 어드민을 지정하거나 해제하고 어드민이 하는 일을 모두 합니다. 어드민은 회사의 관리 단계로, 팀을 만들고 구성원과 룰과 좌석을 관리합니다. 팀을 만든 사람에게도 특별한 권한이 없으며, 팀은 조직의 오너와 어드민, 그리고 슈퍼유저만 관리합니다. 오너와 어드민은 대시보드에서 좌석 한도를 볼 수 있습니다. 좌석을 늘리려면 sales@sprag.io 로 문의합니다.
108
+
109
+ 기존 조직 안에 팀을 만들려면 `sprag team create --org <조직 ID>` 를 씁니다. 명령은 조직 이름과 사용 중인 좌석 수를 함께 출력하고, `sprag team status` 는 조직 이름을 보여 줍니다.
110
+
111
+ ## 업로드 스위치
112
+
113
+ `SPRAG_NO_TEAM_SYNC=1`(옛 이름 `CTS_NO_TEAM_SYNC` 도 계속 읽습니다)을 지정하면 모든 사용량 업로드가 멈춥니다. 참여한 컴퓨터는 스위치가 켜진 것을 처음 봤을 때와 다시 꺼졌을 때, 상태만 담은 호출 `member_set_sync` 를 한 번씩 보냅니다. 사용량 데이터는 들어 있지 않습니다. 다시 꺼진 뒤에는 바로 업로드합니다. 이 호출 덕분에 대시보드가 멈춘 컴퓨터와 연락이 끊긴 컴퓨터를 구분합니다. 멈춘 컴퓨터는 오너나 어드민이 내보내기 전까지 좌석을 유지합니다.
114
+
95
115
  ## 대시보드
96
116
 
97
- https://sprag.io/team/ 에서 소유자가 이메일 코드로 로그인하고, 팀을 만들고, 초대 토큰을 받습니다. 여기서 최근 30일 동안의 구성원별 지출과 절감액을 보고, 룰을 승인하거나 삭제하고, 구성원을 내보낼 수 있습니다.
117
+ https://sprag.io/team/ 에서 오너나 어드민이 이메일 코드로 로그인하고, 팀을 만들고, 초대 토큰을 받습니다. 여기서 구성원별 지출과 절감액을 보고, 규칙 탭에서 조직 룰과 팀 룰을 관리하고, 구성원을 내보낼 수 있습니다. 조직 패널에는 요금제, 한도 대비 사용 좌석, 어드민, 전체 팀이 보입니다. 슈퍼유저는 sprag.io/ops/ 에서 모든 조직을 봅니다.
118
+
119
+ 대시보드 맨 위의 기간 선택(7일, 30일, 90일, 이번 달, 지난 달, 직접 선택)이 멤버 표, 사용량 차트, 프롬프트 사용량 탭의 기간을 정합니다. 기본값은 최근 30일이고, 내 컴퓨터의 달력 날짜를 쓰며 종료일도 포함합니다. 최대 365일까지 고를 수 있습니다. 고른 기간은 주소(`?from=2026-10-01&to=2026-10-18`)에 남으므로 새로고침하거나 링크를 공유해도 같은 기간이 보입니다. sprag.io/ops/ 슈퍼유저 콘솔에도 같은 선택이 있어 조직 표, 조직 머리말, 조직의 프롬프트 사용량에 적용됩니다.
120
+
121
+ ### 좌석 현황
122
+
123
+ 오너와 어드민에게는 /team 에 좌석 현황이 보입니다. 행 하나가 컴퓨터 한 대이고, 다음을 보여 줍니다.
124
+
125
+ - **업로드 스위치.** 켜짐(On) 또는 꺼짐(Off)
126
+ - **신호.** `ok` 는 48시간 안에 업로드한 상태입니다. `late` 는 스위치는 켜져 있는데 48시간 넘게 업로드가 없는 상태입니다. `paused` 는 스위치가 꺼진 상태입니다. `silent` 는 30일 넘게 연락이 없는 상태이며 좌석을 차지하지 않습니다. `left` 는 구성원이 `sprag team leave` 를 실행한 상태, `removed` 는 오너나 어드민이 내보낸 상태입니다.
127
+ - **날짜와 버전.** 마지막 업로드, 마지막 연락, 멈춘 시점, Sprag 버전
128
+
129
+ 행에서 할 수 있는 작업은 다음과 같습니다.
130
+
131
+ - **제거**는 좌석을 비우고, 그 컴퓨터의 키는 더 이상 동작하지 않습니다.
132
+ - **복구**는 오너나 어드민이 내보낸 컴퓨터에만 쓸 수 있고, 좌석 한도 안에서만 됩니다.
133
+ - **좌석 합치기**와 합치기 해제는 위에서 설명한 대로입니다.
134
+
135
+ 현황 아래의 활동 기록에는 참여, 멈춤, 재개, 탈퇴, 제거, 복구가 누가 했는지(구성원, 오너, 어드민, 슈퍼유저)와 함께 남습니다.
98
136
 
99
137
  ## 플랜
100
138
 
101
- Free, Team(좌석당 월 $5), 비즈니스(좌석당 월 $10, 프롬프트 수집 포함)가 있습니다. 자세한 내용은 https://sprag.io/pricing/ 에 있습니다.
139
+ Free, 프로(좌석당 월 $5), 엔터프라이즈(좌석당 월 $10, 프롬프트 수집과 사용자별 프롬프트 사용량 포함)가 있습니다. 대량 구매나 연간 청구는 맞춤 계약이며 sales@sprag.io 로 문의합니다. 자세한 내용은 https://sprag.io/pricing/ 에 있습니다.
140
+
141
+ ## 프롬프트 사용량 (엔터프라이즈)
142
+
143
+ 엔터프라이즈 조직의 오너와 어드민은 sprag.io/team 에서 프롬프트 탭을 씁니다. 사용자별 프롬프트 수, 글자 수, 평균 길이, 활동 일수, 마지막 프롬프트를 표로 보고, 사용자마다 프롬프트를 검색해 볼 수 있습니다. 프롬프트는 프롬프트 수집을 켠 엔터프라이즈 팀만 저장합니다. 프로 조직에는 잠금 화면이 나옵니다.
144
+
145
+ **무엇을 수집하나.** 직접 입력한 프롬프트만 올라갑니다. 메인 Claude Code 세션(`~/.claude/projects/**/*.jsonl`)과 Codex 세션(`<CODEX_HOME 또는 ~/.codex>/sessions/**/*.jsonl`)이 대상입니다. 프롬프트마다 시각, 프로젝트 폴더 이름(경로의 마지막 부분이며 전체 경로는 아닙니다), 본문(최대 20,000자)이 올라갑니다. 어시스턴트 답변, 도구 출력, 파일 내용, 서브에이전트 기록, 슬래시 명령 래퍼, 시스템 리마인더, 파일 경로는 수집하지 않습니다.
146
+
147
+ **비밀값 가림.** 올리기 전에 눈에 띄는 비밀값은 `[redacted]` 로 바꿉니다. API 키와 토큰(`sk-...`, `sk-ant-...`, `ghp_...`, `gho_...`, `github_pat_...`, `AKIA...`, `xox[abpr]-...`), `Bearer` 토큰, 개인 키 블록, `KEY=`, `TOKEN=`, `PASSWORD=`, `SECRET=` 형태의 대입이 대상입니다. 흔한 형태만 잡을 뿐 모든 비밀값을 찾지는 못하므로, 수집이 켜진 컴퓨터에서는 프롬프트에 비밀값을 붙여 넣지 마십시오.
148
+
149
+ **과거분은 올리지 않습니다.** 컴퓨터가 프롬프트 수집이 켜진 것을 처음 보면 기존 기록의 끝에 표시를 하고 그 뒤에 입력한 것만 올립니다. 정책이 이 컴퓨터에 닿기 전에 쓴 내용은 전혀 나가지 않고, 수집이 꺼진 동안 입력한 내용도 나중에 올라가지 않습니다. 그 뒤에 새로 생긴 기록 파일은 처음부터 읽습니다. 정책이 꺼지면 컴퓨터가 읽던 위치(`prompts-offsets.json`)를 지우므로, 다시 켜면 모든 파일의 끝에서 새로 시작합니다.
150
+
151
+ **주기.** 프롬프트는 Claude Code 나 Codex 를 쓰는 동안 약 30분마다 올라가고, 하루 단위 집계는 그대로 6시간마다 올라갑니다. 프롬프트를 제출할 때 작은 로컬 타이머를 확인하고, 30분이 지났으면 분리된 백그라운드 프로세스가 업로드를 시작합니다(훅 안에서는 네트워크 호출도 기록 파싱도 하지 않습니다). 세션을 시작할 때와 `sprag team sync --force` 를 실행할 때도 올라갑니다. 한 번에 최대 2,000건 또는 8MB까지 올리고 나머지는 다음 동기화로 넘어갑니다. 파일 경로는 보내지 않습니다. 파일은 경로 해시의 앞 16자로만 구분하고, 같은 조각을 다시 보내도 서버가 무시합니다.
152
+
153
+ **확인하기.** `sprag team status` 는 "프롬프트 수집: 켜짐"과 마지막 프롬프트 업로드의 시각과 건수를 보여 줍니다. `sprag team status --show-payload` 는 올라갈 차례를 기다리는 프롬프트 수와 예시 하나(시각, 프로젝트, 앞 80자)를 더 보여 주며, 대기 중인 프롬프트의 전체 본문은 출력하지 않습니다.
154
+
155
+ **멈추는 방법.** 오너나 어드민에게 팀의 프롬프트 수집을 꺼 달라고 요청하면 다음 동기화부터 업로드가 멈추고 위치 파일이 지워집니다. 바로 모든 업로드를 멈추려면 `SPRAG_NO_TEAM_SYNC=1` 을 설정하거나 `sprag team leave` 를 실행합니다.
102
156
 
103
157
  ## 개인정보 관련 참고
104
158
 
105
- - 소유자는 구성원별 하루 합계를 봅니다. 세션이나 프롬프트는 보지 못합니다.
159
+ - 오너와 어드민은 구성원별 하루 합계를 봅니다. 엔터프라이즈 조직에서 팀이 프롬프트 수집을 켠 경우를 빼면 세션이나 프롬프트는 보지 못합니다.
106
160
  - 구성원은 `sprag team status --show-payload` 로 같은 내용을 직접 볼 수 있습니다.
107
- - 프롬프트 수집은 비즈니스 정책이며, 소유자가 대시보드에서 켤 수 있습니다. 구성원은 `sprag team status` 에서 설정 상태를 확인합니다. 현재 CLI 는 프롬프트를 아직 올리지 않습니다. 업로드는 이후 별도 릴리스에서 다룹니다.
161
+ - 프롬프트 수집은 엔터프라이즈 정책이며, 오너나 어드민이 대시보드에서 켤 수 있습니다. 구성원은 `sprag team status` 에서 설정 상태를 확인합니다. 수집이 켜져 있으면 CLI 가 정책이 이 컴퓨터에 닿은 뒤 입력한 프롬프트를 올립니다("프롬프트 사용량 (엔터프라이즈)" 절 참고).
108
162
  - 룰 내용은 `sprag team propose` 를 통해서만 밖으로 나갑니다.
109
163
 
110
164
  ## 파일과 환경 변수
@@ -112,8 +166,9 @@ Free, Team(좌석당 월 $5), 비즈니스(좌석당 월 $10, 프롬프트 수
112
166
  | 이름 | 용도 |
113
167
  |---|---|
114
168
  | `<사용자 데이터 디렉터리>/team.json` | 참여 상태와 멤버 키(권한 0600) |
115
- | `<사용자 데이터 디렉터리>/team-rules.json` | 마지막 동기화 때 받은 룰 |
116
- | `~/.claude/ratchet-team.md` | Claude Code 용 승인된 팀 룰이며 `~/.claude/CLAUDE.md` 에서 import 합니다 |
169
+ | `<사용자 데이터 디렉터리>/team-rules.json` | 마지막 동기화 때 받은 조직·팀 룰 |
170
+ | `<사용자 데이터 디렉터리>/prompts-offsets.json` | 프롬프트 업로더가 기록 파일마다 어디까지 읽었는지 적은 파일이며, 프롬프트 수집이 켜진 동안만 있습니다 |
171
+ | `~/.claude/ratchet-team.md` | Claude Code 용 승인된 조직·팀 룰이며 `~/.claude/CLAUDE.md` 에서 import 합니다 |
117
172
  | `<CODEX_HOME>/ratchet-team.md` | Codex 용 같은 룰 |
118
173
  | `SPRAG_TEAM_TOKEN` | 초대 토큰이며, 다음 세션 시작 때 컴퓨터를 등록합니다 |
119
- | `CTS_NO_TEAM_SYNC=1` | 팀 관련 네트워크 통신을 모두 끕니다 |
174
+ | `SPRAG_NO_TEAM_SYNC=1` | 사용량 업로드를 모두 멈춥니다. 참여한 컴퓨터는 스위치가 처음 켜졌을 때와 다시 꺼졌을 때 상태만 담은 호출을 한 번씩 보냅니다(옛 이름 `CTS_NO_TEAM_SYNC` 도 읽습니다) |
package/docs/TEAM.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [← README](../README.md) · [한국어](./TEAM.ko.md)
4
4
 
5
- Sprag is local-only by default. The team layer is an opt-in add-on that lets a group see each member's daily spend and savings in one dashboard, and share the ratchet rules the team owner approves. Nothing is sent until you run `sprag team join <token>` or your environment sets `SPRAG_TEAM_TOKEN`. `CTS_NO_TEAM_SYNC=1` turns off all team network traffic, whatever else is configured.
5
+ Sprag is local-only by default. The team layer is an opt-in add-on that lets a group see each member's daily spend and savings in one dashboard, and share the ratchet rules an owner or admin approves. Nothing is sent until you run `sprag team join <token>` or your environment sets `SPRAG_TEAM_TOKEN`. `SPRAG_NO_TEAM_SYNC=1` stops every usage upload, whatever else is configured.
6
6
 
7
7
  ## What is sent and what is not
8
8
 
@@ -17,12 +17,13 @@ After you join, Sprag uploads one row per local calendar day:
17
17
  | Sprag version | the installed version |
18
18
  | Agent | `claude` or `codex` |
19
19
  | Display name | the name you chose at join; the default is your OS user name |
20
+ | Operating system | OS name and version (for example macOS 26, Windows 11, Ubuntu 24.04) and CPU architecture (arm64 or x64); sent once after joining and again only if it changes. Nothing that identifies the machine by name: no hostname, no user name, no path |
20
21
 
21
- Not sent: prompts, transcripts, file paths, project names, rule text, API keys. A rule leaves your machine only when you run `sprag team propose` yourself.
22
+ Not sent: prompts, transcripts, file paths, project names, rule text, API keys. The one exception is the opt-in prompt upload of an Enterprise team with prompt capture on, described under "Prompt usage (Enterprise)". A rule leaves your machine only when you run `sprag team propose` yourself.
22
23
 
23
- The upload runs at most once every 6 hours, from a detached child process that the SessionStart hook spawns. It never runs on the statusline path.
24
+ The daily aggregate upload runs at most once every 6 hours after the last successful one, from a detached child process that the SessionStart hook (and the prompt hook, which only reads a small local file) spawns. If a sync fails, for example because the network is down, it is retried after 15, then 30, then 60 minutes (60 at most) instead of waiting 6 hours. The CLI remembers the last day it uploaded and re-sends from the day before it, up to 30 days, so a machine that was offline for days catches up on its first successful sync. Re-sending a day is safe because each day is an upsert. The prompt upload of an Enterprise team with capture on has its own 30-minute timer (see "Prompt usage (Enterprise)"). It never runs on the statusline path.
24
25
 
25
- Destination: `https://hiycfjwswtkixyoqkznm.supabase.co/rest/v1/rpc/*` (Supabase, region ap-northeast-2). The calls are the RPCs `team_join`, `ledger_upsert`, `team_rules_get`, `team_rule_propose` and `team_leave`.
26
+ Destination: `https://hiycfjwswtkixyoqkznm.supabase.co/rest/v1/rpc/*` (Supabase, region ap-northeast-2). The calls are the RPCs `team_join`, `ledger_upsert`, `team_rules_get`, `team_rule_propose`, `team_leave`, `prompt_chunk_put` (Enterprise prompt capture only) and `member_set_sync` (the upload-switch status call, see "The upload switch"); `sprag team create` also calls `team_create` with your sign-in session.
26
27
 
27
28
  ## Create a team from the CLI
28
29
 
@@ -48,7 +49,7 @@ By default this machine joins as the first member and runs one sync. Pass `--no-
48
49
 
49
50
  ## Join as an individual
50
51
 
51
- The team owner gives you an invite token. Then:
52
+ An owner or admin gives you an invite token. Then:
52
53
 
53
54
  ```bash
54
55
  sprag team join <token> --name "Mina"
@@ -67,44 +68,98 @@ To enroll every machine without each person running a command, set `SPRAG_TEAM_T
67
68
 
68
69
  The machine joins at the next session start. The first session after that prints a one-line notice that names the team and the two commands that matter: `sprag team status --show-payload` shows what is sent, and `sprag team leave` stops it.
69
70
 
70
- `leave` wins over the environment. A machine that left does not rejoin on its own, even with the variable still set. It rejoins only after the owner issues a new token and the member runs `sprag team join` again.
71
+ `leave` wins over the environment. A machine that left does not rejoin on its own, even with the variable still set. It rejoins only after an owner or admin issues a new token and the member runs `sprag team join` again.
71
72
 
72
73
  ## Team rules
73
74
 
74
- The team owner approves rules in the dashboard. Members can also suggest one:
75
+ Rules come at two levels. **Organization rules** apply to every team in the organization; owners and admins write them on the Rules tab of the dashboard. **Team rules** apply to one team; owners and admins approve them there too. An approved team rule can be applied to the whole organization with one click ("Apply to whole organization"): it moves to the organization level and its team copy is retired, so no machine receives it twice.
76
+
77
+ Members can suggest a team rule:
75
78
 
76
79
  ```bash
77
80
  sprag team propose "Run lint before every commit" --scope ratchet
78
81
  ```
79
82
 
80
- `--scope` is `ratchet` (the default) or `model`. A proposal stays pending until the owner approves it.
83
+ `--scope` is `ratchet` (the default) or `model`. A proposal stays pending until an owner or admin approves it.
84
+
85
+ **When a rule arrives.** Approved rules reach a machine at its next session start. A machine checks for new rules at most every hour and uploads usage every 6 hours; `sprag team sync --force` then a new session applies them at once. Both kinds of sync rewrite the rule files as soon as the rules arrive, so the next session reads them without another round trip. `sprag team sync --rules-only` pulls the rules alone and uploads nothing.
81
86
 
82
- Approved rules come back with the next sync. Sprag writes them to `~/.claude/ratchet-team.md` (Codex: `<CODEX_HOME>/ratchet-team.md`) and imports that file from the global harness block of `~/.claude/CLAUDE.md` for as long as the file exists. Do not edit the file by hand, because Sprag rewrites it at session start. `sprag team rules` lists the cached rules. `sprag team leave` removes the member key, the cache and the import.
87
+ Sprag writes the rules to `~/.claude/ratchet-team.md` (Codex: `<CODEX_HOME>/ratchet-team.md`), organization rules first and then team rules, each with its model rules in a sub-section, and imports that file from the global harness block of `~/.claude/CLAUDE.md` for as long as the file exists. Do not edit the file by hand, because Sprag rewrites it. `sprag team rules` lists the cached rules as `- [org · model] text` or `- [team · ratchet] text`. `sprag team leave` removes the member key, the cache and the import.
83
88
 
84
89
  ## Status, payload and leaving
85
90
 
86
91
  | Command | What it does |
87
92
  |---|---|
88
93
  | `sprag team join [token] [--name <n>] [--yes]` | Join a team |
89
- | `sprag team status [--show-payload]` | Team, plan, prompt-capture flag, display name, last sync. `--show-payload` also prints the exact daily rows that would be uploaded |
90
- | `sprag team sync [--force]` | Upload now and pull rules. `--force` ignores the 6-hour interval |
91
- | `sprag team rules` | Show the cached team rules |
94
+ | `sprag team status [--show-payload]` | Team, plan, prompt-capture flag, display name, last sync. `--show-payload` also prints the device info and the exact daily rows that would be uploaded |
95
+ | `sprag team sync [--force] [--rules-only]` | Upload now and pull rules. `--force` ignores the 6-hour interval; `--rules-only` pulls the organization and team rules and uploads nothing |
96
+ | `sprag team rules` | Show the cached organization and team rules |
92
97
  | `sprag team propose "<text>" [--scope ratchet\|model]` | Propose a rule to the team |
93
98
  | `sprag team leave` | Leave the team and stop uploading |
94
99
 
100
+ ## Organizations and seats
101
+
102
+ Every team belongs to an organization, and seats are bought per organization, not per team. Teams you create without naming an organization get a new one automatically, so existing setups keep working.
103
+
104
+ - **What a seat is.** One machine (one member key) that has not left and either sent anything in the last 30 days or is paused. A machine that has been silent for 30 days or more without being paused holds no seat. Members who left are not counted.
105
+ - **Second machine.** When the same person uses two machines, an owner or admin can merge the second one into the first so the two share one seat. In the dashboard, use "Merge seat" on the member row; "Unmerge" gives it its own seat back. A merged row shows "shares a seat with <name>".
106
+ - **The limit.** Each organization has a seat limit. A join past the limit is refused: `sprag team join` exits 1 with "This organization has no free seat. Ask your organization owner or admin to add seats", and an environment auto-join stays silent and tries again at a later session start. Lowering the limit below current use never removes anyone; it only refuses new joins until Sprag raises it again.
107
+ - **Roles.** There are three permission levels. The superuser is Sprag staff: it sets the plan and the seat limit, suspends organizations, adds owners, and sees every organization at sprag.io/ops/. An owner is a company's top level: it appoints and removes admins, and does everything an admin does. An admin is a company's management level: it creates teams and manages members, rules and seats. The person who created a team has no special rights over it; only the organization's owners and admins and the superuser manage teams. Owners and admins see the seat limit in the dashboard. To add seats, write to sales@sprag.io.
108
+ - **The plan.** Sprag sets the plan (`pro` or `enterprise`) on the organization; owners and admins cannot change it. Prompt capture can be switched on only while the organization is on Enterprise. To change the plan, contact us.
109
+
110
+ Create a team inside an existing organization from the CLI with `sprag team create --org <org-id>`. The command prints the organization name and how many seats are used. `sprag team status` shows the organization name.
111
+
112
+ ## The upload switch
113
+
114
+ `SPRAG_NO_TEAM_SYNC=1` (the old name `CTS_NO_TEAM_SYNC` is still read) stops every usage upload. A joined machine sends one status-only call, `member_set_sync`, with no usage data, the first time it sees the switch on, and one more when it is off again. After that it uploads immediately. That call is how the dashboard can tell a paused machine from a silent one. A paused machine keeps its seat until an owner or admin removes it.
115
+
95
116
  ## The dashboard
96
117
 
97
- https://sprag.io/team/ is where the owner signs in with an email code, creates a team and gets the invite token. From there the owner sees per-member spend and savings for the last 30 days, approves or removes rules, and removes members.
118
+ https://sprag.io/team/ is where an owner or admin signs in with an email code, creates a team and gets the invite token. From there they see per-member spend and savings, manage organization and team rules on the Rules tab, and remove members. The organization panel shows the plan, seats used against the limit, admins and every team. The superuser sees every organization at sprag.io/ops/.
119
+
120
+ A date range control at the top of the dashboard (7d, 30d, 90d, This month, Last month or Custom) sets the period for the members table, the Usage chart and the prompt usage tab. It defaults to the last 30 days, uses your local calendar days with the end date included, and allows 365 days at most. The choice is kept in the page address (`?from=2026-10-01&to=2026-10-18`), so a reload or a shared link shows the same period. The superuser console at /ops/ has the same control for the organization table, the organization header and its prompt usage.
121
+
122
+ ### The seat roster
123
+
124
+ Owners and admins get a seat roster on /team. Each row is one machine and shows:
125
+
126
+ - **Upload switch.** On or Off.
127
+ - **Signal.** `ok`: an upload within 48 hours. `late`: the switch is on but there has been no upload for 48 hours or more. `paused`: the switch is off. `silent`: no contact for 30 days or more; the machine holds no seat. `left`: the member ran `sprag team leave`. `removed`: an owner or admin removed it.
128
+ - **Dates and version.** Last upload, last contact, paused since, and the Sprag version.
129
+
130
+ Row actions:
131
+
132
+ - **Remove** frees the seat, and the machine's key stops working.
133
+ - **Restore** brings back only machines that an owner or admin removed, and only within the seat limit.
134
+ - **Seat merge** and unmerge, as described above.
135
+
136
+ Below the roster, an activity log lists joined, paused, resumed, left, removed and restored events, with who did each one: member, owner, admin or superuser.
98
137
 
99
138
  ## Plans
100
139
 
101
- Free, Team ($5 per seat per month) and Business ($10 per seat per month, with prompt capture). Details are on https://sprag.io/pricing/.
140
+ Free, Pro ($5 per seat per month) and Enterprise ($10 per seat per month, with prompt capture and per-user prompt usage). Volume or annual invoicing is a custom agreement; write to sales@sprag.io. Details are on https://sprag.io/pricing/.
141
+
142
+ ## Prompt usage (Enterprise)
143
+
144
+ Owners and admins of an Enterprise organization get a Prompts tab on sprag.io/team: a per-user table of prompts, characters, average length, active days and last prompt, and a searchable prompt list per user. Prompts are stored only for Enterprise teams that have prompt capture switched on; Pro organizations see a locked state.
145
+
146
+ **What is captured.** Only the prompts you typed yourself, from your main Claude Code sessions (`~/.claude/projects/**/*.jsonl`) and Codex sessions (`<CODEX_HOME or ~/.codex>/sessions/**/*.jsonl`). Each prompt goes up as its time, the project folder's name (the last part of the path, never the full path) and its text, cut at 20,000 characters. Not captured: assistant replies, tool output, file contents, subagent transcripts, slash-command wrappers, system reminders, or any file path.
147
+
148
+ **Redaction.** Before upload, obvious secrets are replaced with `[redacted]`: API keys and tokens (`sk-...`, `sk-ant-...`, `ghp_...`, `gho_...`, `github_pat_...`, `AKIA...`, `xox[abpr]-...`), `Bearer` tokens, private key blocks, and `KEY=`, `TOKEN=`, `PASSWORD=` and `SECRET=` assignments. This catches common shapes, not every secret, so do not paste secrets into prompts on a machine with capture on.
149
+
150
+ **No backfill.** The first time a machine sees prompt capture on, it marks the end of every existing transcript and uploads only what you type after that. Nothing written before the policy reached your machine is sent, and what you type while capture is off is never sent later. Transcripts created afterwards are read from their start. When the policy turns off, the machine forgets its place (`prompts-offsets.json`), so switching it on again starts fresh at the end of every file.
151
+
152
+ **Cadence.** Prompts are uploaded about every 30 minutes while Claude Code or Codex is in use; the daily aggregate stays at every 6 hours. A prompt you submit checks a small local timer and, when 30 minutes have passed, starts a detached background upload (no network and no parsing happen in the hook itself). A session start and `sprag team sync --force` also upload. One run sends at most 2,000 prompts or 8 MB; the rest waits for the next sync. The file path is never sent; each file is identified by the first 16 characters of a hash of its path, and a repeated chunk is ignored by the server.
153
+
154
+ **Check it.** `sprag team status` shows "Prompt capture: on" and the time and count of the last prompt upload. `sprag team status --show-payload` also shows how many prompts are waiting and one example (time, project, first 80 characters), never the full text of the pending prompts.
155
+
156
+ **How to stop.** Ask an owner or admin to switch prompt capture off for the team (the next sync stops the upload and clears the position file); set `SPRAG_NO_TEAM_SYNC=1` to stop every upload right away; or run `sprag team leave`.
102
157
 
103
158
  ## Privacy notes
104
159
 
105
- - The owner sees per-member daily totals, not sessions or prompts.
160
+ - Owners and admins see per-member daily totals, not sessions or prompts, unless the organization is on Enterprise and the team has prompt capture switched on.
106
161
  - A member sees the same payload with `sprag team status --show-payload`.
107
- - Prompt capture is a Business policy that the owner can switch on in the dashboard. Members see its state in `sprag team status`. The current CLI does not upload prompts yet; that is a separate, later release.
162
+ - Prompt capture is an Enterprise policy that an owner or admin can switch on in the dashboard. Members see its state in `sprag team status`. When capture is on, the CLI uploads the prompts you type after the policy reached your machine (see "Prompt usage (Enterprise)").
108
163
  - Rule text leaves your machine only through `sprag team propose`.
109
164
 
110
165
  ## Files and environment variables
@@ -112,8 +167,9 @@ Free, Team ($5 per seat per month) and Business ($10 per seat per month, with pr
112
167
  | Name | Purpose |
113
168
  |---|---|
114
169
  | `<user data dir>/team.json` | Membership state and the member key (mode 0600) |
115
- | `<user data dir>/team-rules.json` | Rules pulled at the last sync |
116
- | `~/.claude/ratchet-team.md` | Approved team rules for Claude Code, imported from `~/.claude/CLAUDE.md` |
170
+ | `<user data dir>/team-rules.json` | Organization and team rules pulled at the last sync |
171
+ | `<user data dir>/prompts-offsets.json` | Where the prompt uploader stopped in each transcript; exists only while prompt capture is on |
172
+ | `~/.claude/ratchet-team.md` | Approved organization and team rules for Claude Code, imported from `~/.claude/CLAUDE.md` |
117
173
  | `<CODEX_HOME>/ratchet-team.md` | The same rules for Codex |
118
174
  | `SPRAG_TEAM_TOKEN` | Invite token; enrolls the machine at the next session start |
119
- | `CTS_NO_TEAM_SYNC=1` | Disables all team network traffic |
175
+ | `SPRAG_NO_TEAM_SYNC=1` | Stops every usage upload. A joined machine sends only one status-only call when it first sees the switch on and when it is off again (old name `CTS_NO_TEAM_SYNC` still read) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprag-cli",
3
- "version": "3.70.0",
3
+ "version": "3.71.0",
4
4
  "description": "Model-delegation router for Claude Code and Codex: lookups, checks and test runs go to cheaper models, documents go in as compact Markdown, and a live statusline shows spend and savings.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "deploy": "node scripts/deploy.mjs",
12
12
  "docs:statusline": "node scripts/docs-statusline.mjs",
13
13
  "postinstall": "node bin/postinstall.js || true",
14
+ "presets:pull": "node scripts/presets-pull.mjs",
14
15
  "publish:legacy": "node scripts/publish-legacy.mjs",
15
16
  "release:notes": "node scripts/release-notes.mjs",
16
17
  "site:docs": "node scripts/build-site-docs.mjs",