sprag-cli 3.78.0 → 3.79.1

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.ko.md CHANGED
@@ -258,13 +258,44 @@ sprag feedback "Bedrock에서 5m TTL 칩이 사라지지 않아요"
258
258
 
259
259
  ## 팀
260
260
 
261
- Sprag 은 켜지 않으면 로컬에서만 동작합니다. `sprag team join <토큰>` 을 실행하거나(조직 전체는 `SPRAG_TEAM_TOKEN`) 하면 컴퓨터마다 하루에 한 행씩 올라갑니다. 내용은 지출, 절감액, 위임 실행 횟수, 모델 계열 비중입니다. 프롬프트, 대화 기록, 파일 경로, 프로젝트 이름은 보내지 않으며, `sprag team status --show-payload` 로 올라갈 내용을 그대로 볼 수 있습니다. 그 대신 팀 소유자는 대시보드에서 구성원별 합계를 보고, 모든 구성원에게 자동으로 적용될 래칫 룰을 승인할 수 있습니다.
261
+ CLI는 무료이고 컴퓨터 한 대에서 혼자 동작합니다. 조직에는 **Sprag Pro** 와 **Sprag Enterprise** 가 같은 로컬 세션 로그로 만든 공용 대시보드를 더해 줍니다. 도입은 [sales@sprag.io](mailto:sales@sprag.io)로 문의해 주세요.
262
+
263
+ ### Pro: 팀 대시보드
264
+
265
+ <a href="https://sprag.io/assets/video/plan-pro-ko.mp4"><img src="https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/plan-pro-ko.gif" width="640" alt="팀원마다 동물 캐릭터로 앉아 있는 가상 사무실, 지출과 절감액, 좌석, 30일 차트가 있는 조직 대시보드, 한 번 승인하면 모든 컴퓨터에 적용되는 팀 규칙을 차례로 보여 줍니다."></a>
266
+
267
+ - 조직 대시보드와 30일 추이 차트
268
+ - 사람마다 동물 캐릭터가 앉아 있는 가상 사무실
269
+ - 컴퓨터별 상태가 보이는 좌석 명단
270
+ - 세션 시작 때 적용되는 팀 승인 규칙
271
+ - `sprag team join` 으로 조직 배포
272
+ - 오너와 어드민 역할, 활동 기록
273
+
274
+ ### Enterprise: 프롬프트 모니터링
275
+
276
+ <a href="https://sprag.io/assets/video/plan-ent-ko.mp4"><img src="https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/plan-ent-ko.gif" width="640" alt="사용자별 프롬프트 사용량과 30일 추이, 한 사람의 프롬프트 검색, 팀별로 프롬프트 수집을 켜는 설정을 보여 줍니다."></a>
277
+
278
+ Pro 의 기능에 더해 다음을 제공합니다.
279
+
280
+ - 팀별 프롬프트 수집, 비밀 값은 가림
281
+ - 정책을 켜기 전 기록은 수집하지 않음
282
+ - 사용자별 프롬프트 사용량과 30일 추이
283
+ - 사용자별 프롬프트 목록과 검색
284
+ - 모든 멤버에게 보이는 수집 정책
285
+
286
+ 프롬프트 수집은 조직 오너나 어드민이 켜기 전까지 꺼져 있습니다.
287
+
288
+ <sub>두 영상 모두 데모 대시보드에서 예시 데이터로 녹화했습니다. 전체 영상: <a href="https://sprag.io/assets/video/plan-pro-ko.mp4">Pro (30초)</a> · <a href="https://sprag.io/assets/video/plan-ent-ko.mp4">Enterprise (30초)</a></sub>
289
+
290
+ ### 컴퓨터가 보내는 것
291
+
292
+ Sprag 은 켜지 않으면 로컬에서만 동작합니다. `sprag team join <토큰>` 을 실행하거나(조직 전체는 `SPRAG_TEAM_TOKEN`) 하면 컴퓨터마다 하루에 한 행씩 올라갑니다. 내용은 지출, 절감액, 위임 실행 횟수, 모델 계열 비중입니다. 대화 기록, 파일 경로, 프로젝트 이름은 보내지 않습니다. 프롬프트도 Enterprise 조직이 수집 정책을 켜지 않는 한 보내지 않으며, `sprag team status --show-payload` 로 올라갈 내용을 그대로 볼 수 있습니다. 그 대신 팀 소유자는 대시보드에서 구성원별 합계를 보고, 모든 구성원에게 자동으로 적용될 래칫 룰을 승인할 수 있습니다.
262
293
 
263
294
  `SPRAG_NO_TEAM_SYNC=1` 은 팀 관련 통신을 모두 끄고, `sprag team leave` 는 참여를 해제합니다.
264
295
 
265
296
  - [팀 기능 안내](docs/TEAM.ko.md)
266
- - 대시보드: https://sprag.io/team/
267
- - 플랜과 가격: https://sprag.io/pricing/
297
+ - 오너와 어드민용 팀 콘솔: https://sprag.io/team/
298
+ - 플랜 안내: https://sprag.io/license/
268
299
 
269
300
  ## 라이선스
270
301
 
package/README.md CHANGED
@@ -312,13 +312,44 @@ For a bug, please include the tool version (`sprag --version`) and your OS. For
312
312
 
313
313
  ## Teams
314
314
 
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.
315
+ The CLI is free and works on its own on one machine. For organizations, **Sprag Pro** and **Sprag Enterprise** add a shared dashboard built from the same local session logs. To start, write to [sales@sprag.io](mailto:sales@sprag.io).
316
+
317
+ ### Pro: team dashboard
318
+
319
+ <a href="https://sprag.io/assets/video/plan-pro-en.mp4"><img src="https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/plan-pro-en.gif" width="640" alt="A virtual office where every teammate sits as an animal character, then the organization dashboard with spend, savings, seats and a 30-day chart, then team rules approved once and picked up by every machine."></a>
320
+
321
+ - Organization dashboard with a 30-day chart
322
+ - A virtual office with an animal for each person
323
+ - Seat roster with each machine's status
324
+ - Team-approved rules applied at session start
325
+ - Rollout with `sprag team join`
326
+ - Owner and admin roles, activity log
327
+
328
+ ### Enterprise: prompt monitoring
329
+
330
+ <a href="https://sprag.io/assets/video/plan-ent-en.mp4"><img src="https://raw.githubusercontent.com/rootstudioyaml/sprag/main/docs/plan-ent-en.gif" width="640" alt="Per-user prompt usage with a 30-day trend, a prompt search for one person, and the team setting that turns prompt capture on."></a>
331
+
332
+ Everything in Pro, plus:
333
+
334
+ - Prompt capture per team, with secrets redacted
335
+ - Nothing from before the policy was turned on
336
+ - Per-user prompt usage and a 30-day trend
337
+ - Each user's prompt list with search
338
+ - A capture policy every member can see
339
+
340
+ Prompt capture is off unless an organization owner or admin turns it on.
341
+
342
+ <sub>Both videos are recorded from the demo dashboard with sample data. Full videos: <a href="https://sprag.io/assets/video/plan-pro-en.mp4">Pro (30s)</a> · <a href="https://sprag.io/assets/video/plan-ent-en.mp4">Enterprise (30s)</a></sub>
343
+
344
+ ### What a machine sends
345
+
346
+ 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. Transcripts, file paths and project names are never sent. Prompts are not sent either, unless an Enterprise organization turns on prompt capture, 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.
316
347
 
317
348
  `SPRAG_NO_TEAM_SYNC=1` turns all team traffic off, and `sprag team leave` removes the membership.
318
349
 
319
350
  - [Team layer guide](docs/TEAM.md)
320
- - Dashboard: https://sprag.io/team/
321
- - Plans and pricing: https://sprag.io/pricing/
351
+ - Team console for owners and admins: https://sprag.io/team/
352
+ - Plans: https://sprag.io/license/
322
353
 
323
354
  ## License
324
355
 
package/docs/TEAM.ko.md CHANGED
@@ -117,7 +117,7 @@ Sprag 은 룰을 `~/.claude/ratchet-team.md`(Codex 는 `<CODEX_HOME>/ratchet-tea
117
117
 
118
118
  ## 대시보드
119
119
 
120
- https://sprag.io/team/ 에서 등록된 오너나 어드민이 이메일 코드로 로그인하고, 팀을 만들고, 초대 토큰을 받습니다. 가입 절차는 없습니다. 오너, 어드민, 초대 대기 중 어느 쪽에도 없는 이메일은 코드를 보내기 전에 거절하며, 조직 오너에게 추가를 요청하라고 안내합니다. 초대받은 사람은 처음 로그인할 때 자동으로 오너나 어드민이 됩니다. 여기서 구성원별 지출과 절감액을 보고, 규칙 탭에서 조직 룰과 팀 룰을 관리하고, 구성원을 내보낼 수 있습니다. 조직 패널에는 요금제, 한도 대비 사용 좌석, 어드민, 전체 팀이 보입니다. 슈퍼유저는 sprag.io/ops/ 에서 모든 조직을 봅니다.
120
+ https://sprag.io/team/ 에서 등록된 오너나 어드민이 이메일 코드로 로그인하고, 팀을 만들고, 초대 토큰을 받습니다. 새 조직은 sales@sprag.io 로 문의합니다. 오너, 어드민, 초대 대기 중 어느 쪽에도 없는 이메일은 코드를 보내기 전에 거절하며, 조직 오너에게 추가를 요청하라고 안내합니다. 초대받은 사람은 처음 로그인할 때 자동으로 오너나 어드민이 됩니다. 여기서 구성원별 지출과 절감액을 보고, 규칙 탭에서 조직 룰과 팀 룰을 관리하고, 구성원을 내보낼 수 있습니다. 조직 패널에는 요금제, 한도 대비 사용 좌석, 어드민, 전체 팀이 보입니다. 슈퍼유저는 sprag.io/ops/ 에서 모든 조직을 봅니다.
121
121
 
122
122
  대시보드 맨 위의 기간 선택(7일, 30일, 90일, 이번 달, 지난 달, 직접 선택)이 멤버 표, 사용량 차트, 프롬프트 사용량 탭의 기간을 정합니다. 기본값은 최근 30일이고, 내 컴퓨터의 달력 날짜를 쓰며 종료일도 포함합니다. 최대 365일까지 고를 수 있습니다. 고른 기간은 주소(`?from=2026-10-01&to=2026-10-18`)에 남으므로 새로고침하거나 링크를 공유해도 같은 기간이 보입니다. sprag.io/ops/ 슈퍼유저 콘솔에도 같은 선택이 있어 조직 표, 조직 머리말, 조직의 프롬프트 사용량에 적용됩니다.
123
123
 
package/docs/TEAM.md CHANGED
@@ -118,7 +118,7 @@ Create a team inside an organization you administer from the CLI with `sprag tea
118
118
 
119
119
  ## The dashboard
120
120
 
121
- https://sprag.io/team/ is where a registered owner or admin signs in with an email code, creates a team and gets the invite token. There is no sign-up: an email that is not an owner, an admin or a pending invite is refused before any code is sent, with a message to ask the organization's owner to add it. Invited people become owner or admin automatically on their first sign-in. 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/.
121
+ https://sprag.io/team/ is where a registered owner or admin signs in with an email code, creates a team and gets the invite token. A new organization starts by writing to sales@sprag.io. An email that is not an owner, an admin or a pending invite is refused before any code is sent, with a message to ask the organization's owner to add it. Invited people become owner or admin automatically on their first sign-in. 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/.
122
122
 
123
123
  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.
124
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprag-cli",
3
- "version": "3.78.0",
3
+ "version": "3.79.1",
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": {
@@ -15,7 +15,7 @@ import { pendingPrompts } from '../team-prompts.js';
15
15
  import { memberId } from '../member-id.js';
16
16
  import {
17
17
  loadTeamState, deviceInfo, isJoined, joinTeam, leaveTeam, syncTeam, autoJoin, aggregateDaily, rpc,
18
- teamRulesCachePath, teamSyncDisabled, requestSignInCode, verifySignInCode, createTeam, isSeatLimitError,
18
+ teamRulesCachePath, teamSyncDisabled, requestSignInCode, verifySignInCode, createTeam, isSeatLimitError, isOrgExpiredError,
19
19
  } from '../team-sync.js';
20
20
 
21
21
  const T = {
@@ -40,6 +40,7 @@ const T = {
40
40
  aborted: 'Not joined.',
41
41
  joined: (n) => `Joined team "${n}". Check it with: sprag team status`,
42
42
  seatLimit: 'This organization has no free seat. Ask your organization owner or admin to add seats, then join again.',
43
+ orgExpired: "This organization's paid term has ended, so the server no longer accepts uploads or joins. Ask your organization owner to renew it (sales@sprag.io).",
43
44
  org: 'Organization',
44
45
  seats: (u, l) => `${u}/${l} seats used`,
45
46
  orgCreated: (n, u, l) => `Organization: ${n}${u != null && l != null ? ` (${u}/${l} seats used)` : ''}`,
@@ -105,6 +106,7 @@ const T = {
105
106
  aborted: '참여하지 않았습니다.',
106
107
  joined: (n) => `팀 "${n}" 에 참여했습니다. 확인: sprag team status`,
107
108
  seatLimit: '이 조직에 남은 좌석이 없습니다. 조직 오너나 어드민에게 좌석 추가를 요청한 뒤 다시 참여하세요',
109
+ orgExpired: '조직의 이용 기간이 끝나 서버가 업로드와 참여를 받지 않습니다. 조직 오너에게 연장을 요청하세요(sales@sprag.io)',
108
110
  org: '조직',
109
111
  seats: (u, l) => `좌석 ${u}/${l} 사용 중`,
110
112
  orgCreated: (n, u, l) => `조직: ${n}${u != null && l != null ? ` (좌석 ${u}/${l} 사용 중)` : ''}`,
@@ -284,7 +286,7 @@ export async function run({ args, hasFlag, getArg, version, fetch, interactive,
284
286
  }
285
287
  out(`${m.name}: ${st.displayName}`);
286
288
  out(st.lastSyncAt ? m.lastSync(new Date(st.lastSyncAt).toLocaleString(), st.lastSyncRows || 0) : m.neverSynced);
287
- if (st.lastError) out(`${m.lastError}: ${st.lastError}`);
289
+ if (st.lastError) out(isOrgExpiredError(st.lastError) ? m.orgExpired : `${m.lastError}: ${st.lastError}`);
288
290
  if (hasFlag('--show-payload')) {
289
291
  out(`device: ${JSON.stringify(st.deviceInfo || deviceInfo())}`);
290
292
  out(JSON.stringify(await aggregateDaily(), null, 2));
@@ -336,7 +338,7 @@ export async function run({ args, hasFlag, getArg, version, fetch, interactive,
336
338
  return verb && verb !== 'help' ? process.exit(2) : undefined;
337
339
  }
338
340
  } catch (e) {
339
- console.error(isSeatLimitError(e) ? m.seatLimit : e.message);
341
+ console.error(isSeatLimitError(e) ? m.seatLimit : isOrgExpiredError(e) ? m.orgExpired : e.message);
340
342
  return process.exit(1);
341
343
  }
342
344
  }
package/src/team-sync.js CHANGED
@@ -247,6 +247,9 @@ export class SeatLimitError extends Error {
247
247
 
248
248
  export const isSeatLimitError = (e) => !!e && (e.code === 'SEAT_LIMIT' || /seat limit reached/i.test(String(e.message || '')));
249
249
 
250
+ /** The server refuses uploads and joins 14 days after an organization's paid term ends ('org_expired: ...'). */
251
+ export const isOrgExpiredError = (e) => /\borg_expired\b/.test(String((e && e.message) || e || ''));
252
+
250
253
  /** Local calendar day, same key as stats.js. */
251
254
  function dayKey(ms) {
252
255
  const d = new Date(ms);