shraga 0.1.1 โ†’ 0.1.3

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 CHANGED
@@ -2,21 +2,35 @@
2
2
 
3
3
  # ๐Ÿง‘โ€๐Ÿ’ป Shraga
4
4
 
5
- **The teammate you delegate coding to โ€” just forward it to Shraga.**
5
+ **The teammate you delegate coding to. Just forward it to Shraga.**
6
6
 
7
7
  [![npm](https://img.shields.io/npm/v/shraga.svg)](https://www.npmjs.com/package/shraga)
8
8
  [![CI](https://github.com/Livshitz/shraga/actions/workflows/ci.yml/badge.svg)](https://github.com/Livshitz/shraga/actions/workflows/ci.yml)
9
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
10
9
 
11
10
  </div>
12
11
 
13
- Shraga is a self-hostable, multi-user web UI for running an AI coding agent โ€” Claude Code out
14
- of the box, with a pluggable engine seam for other runtimes โ€” that you can reach from anywhere:
15
- your laptop, a home server, or a VM.
12
+ Shraga is a self-hostable, multi-user web UI for running an AI coding agent (Claude Code out of
13
+ the box, with a pluggable engine seam for other runtimes) that you can reach from anywhere: your
14
+ laptop, a home server, or a VM.
16
15
 
17
- Give it its own machine, its own keys, its own identity โ€” onboard it once and delegate.
16
+ Give it its own machine, its own keys, its own identity. Onboard it once, then delegate.
18
17
 
19
- ---
18
+ ## Why Shraga
19
+
20
+ - โญ **Runs on your Claude subscription, not a metered API key.** Point it at Claude Code and it
21
+ drives the agent through your existing plan (`claude auth login`). No per-token bill to watch.
22
+ - โญ **A real teammate, not a chat box.** It has its own machine, its own identity, and its own
23
+ logins, so you delegate a task the way you would to a person and come back to the result.
24
+ - โญ **Multi-user by design.** One Shraga serves a whole team, each with their own sessions,
25
+ tools, and permissions.
26
+ - โญ **A shared brain that compounds.** It onboards, learns your codebase and conventions, and
27
+ carries that knowledge forward across sessions and teammates.
28
+ - **Chat with an agent** that has your workspace, shell, and tools.
29
+ - **Sessions** you can fork, resume, and revisit.
30
+ - **Skills**: reusable procedures you teach it once (files in `data/skills/`).
31
+ - **MCP servers**: connect tools (GitHub, Slack, databases) per-user or globally.
32
+ - **Schedules**: run agent jobs on a cron or in response to events.
33
+ - **Programmatic API + MCP endpoint**: drive the agent from scripts or from claude.ai.
20
34
 
21
35
  ## Quickstart
22
36
 
@@ -25,85 +39,67 @@ Requires [Bun](https://bun.sh) โ‰ฅ 1.0.
25
39
  ```bash
26
40
  # 1. Install & configure
27
41
  bun install
28
- cp .env.example .env # then set ANTHROPIC_API_KEY
42
+ cp .env.example .env
29
43
 
30
44
  # 2. Run
31
45
  bun run build # build the web UI
32
46
  bun run start # serve on http://localhost:3032
33
47
  ```
34
48
 
35
- Open http://localhost:3032. On first run you'll create your account (local username/password โ€”
36
- no external auth needed). Or seed one from the CLI:
49
+ Open http://localhost:3032. On first run you create your account (local username and password, no
50
+ external auth needed). Or seed one from the CLI:
37
51
 
38
52
  ```bash
39
53
  shraga user add you@example.com <password>
40
54
  ```
41
55
 
42
- That's it โ€” sign in and start delegating.
56
+ Sign in and start delegating.
43
57
 
44
- > Prefer subscription auth? Run `claude auth login` and leave `ANTHROPIC_API_KEY` unset.
58
+ ### Auth: subscription or API key
45
59
 
46
- ## What you get
60
+ Shraga runs the agent through **Claude Code**, so it uses whatever Claude Code is authenticated
61
+ with:
47
62
 
48
- - **Chat with an agent** that has your workspace, shell, and tools.
49
- - **Sessions** โ€” fork, resume, and revisit past conversations.
50
- - **Skills** โ€” teach the agent reusable procedures (files in `data/skills/`).
51
- - **MCP servers** โ€” connect tools (GitHub, Slack, databases, โ€ฆ) per-user or globally.
52
- - **Schedules** โ€” run agent jobs on a cron or in response to events.
53
- - **Programmatic API + MCP endpoint** โ€” drive the agent from scripts or `claude.ai`.
63
+ - **Your Claude subscription (recommended).** Run `claude auth login` once and leave
64
+ `ANTHROPIC_API_KEY` unset. The agent runs on your plan, with no metered API charges.
65
+ - **An API key.** Set `ANTHROPIC_API_KEY` in `.env` if you would rather pay per token.
54
66
 
55
67
  ## Configuration
56
68
 
57
- All config is flat files under `data/` (no database). See `.env.example` for options.
58
- Key ones:
69
+ All config is flat files under `data/` (no database). See `.env.example` for the full list. The
70
+ common ones:
59
71
 
60
72
  | Var | Default | Purpose |
61
73
  |-----|---------|---------|
62
- | `ANTHROPIC_API_KEY` | โ€” | Claude API key (or `claude auth login`) |
74
+ | `ANTHROPIC_API_KEY` | *(unset)* | Claude API key. Leave unset to use `claude auth login`. |
63
75
  | `PORT` | `3032` | Server port |
64
76
  | `DATA_DIR` | `./data` | Where state lives |
65
77
  | `AUTH_PROVIDER` | `local` | `local` (username/password) or `firebase` |
66
- | `OWNERS` | โ€” | Comma-list of admin emails |
67
-
68
- ### Optional features
69
-
70
- Shraga's core is a focused chat workspace. Heavier surfaces are opt-in via env flags:
71
-
72
- | Flag | Enables |
73
- |------|---------|
74
- | `SHRAGA_FEAT_WORKSPACE=1` | Multi-tab workspace |
75
- | `SHRAGA_FEAT_INSTANCES=1` | Multi-instance switcher |
76
-
77
- Some surfaces are supplied by add-ons rather than the core โ€” e.g. embedded terminals and voice
78
- mode. Their capability flags appear once the corresponding add-on is installed; on a bare core
79
- build the flags are inert.
78
+ | `OWNERS` | *(unset)* | Comma-separated list of admin emails |
80
79
 
81
80
  ## Expose it (optional)
82
81
 
83
- Running on a home machine? Set `CLOUDFLARE_TUNNEL_TOKEN` to get a public URL via
84
- Cloudflare Tunnel, or put it behind any reverse proxy.
82
+ Running on a home machine? Set `CLOUDFLARE_TUNNEL_TOKEN` for a public URL via Cloudflare Tunnel, or
83
+ put it behind any reverse proxy.
85
84
 
86
85
  ## Architecture
87
86
 
88
- - **Runtime:** Bun ยท **Server:** Express + WebSocket (`src/server/`)
87
+ - **Runtime:** Bun. **Server:** Express + WebSocket (`src/server/`)
89
88
  - **Client:** React + Vite + Tailwind (`src/client/`)
90
89
  - **Agent:** `@anthropic-ai/claude-agent-sdk`
91
- - **Storage:** flat JSON/JSONL in `data/` โ€” no database
90
+ - **Storage:** flat JSON/JSONL in `data/` (no database)
92
91
  - **Auth:** pluggable provider (local by default; Firebase optional)
93
92
 
94
93
  ## Read the thinking
95
94
 
96
- Shraga is a set of ideas before it's a binary:
95
+ Shraga is a set of ideas before it is a binary:
97
96
 
98
- - **[Concept](./docs/concept.md)** โ€” the mental model: delegation, not another chat box.
99
- - **[The shared brain](./docs/shared-brain.md)** โ€” a teammate that onboards, learns, and compounds org knowledge.
100
- - **[Primitives](./docs/architecture/primitives.md)** โ€” the core nouns you build with.
97
+ - **[Concept](./docs/concept.md)**: the mental model of delegation, not another chat box.
98
+ - **[The shared brain](./docs/shared-brain.md)**: a teammate that onboards, learns, and compounds
99
+ org knowledge.
100
+ - **[Primitives](./docs/architecture/primitives.md)**: the core nouns you build with.
101
101
 
102
102
  ## Feedback
103
103
 
104
- Concepts, gaps, "why not X", use cases โ†’ [Discussions](https://github.com/Livshitz/shraga/discussions).
105
- Bugs and concrete proposals โ†’ [Issues](https://github.com/Livshitz/shraga/issues).
106
-
107
- ## License
108
-
109
- [MIT](./LICENSE) ยฉ Elya Livshitz
104
+ Concepts, gaps, "why not X", use cases: [Discussions](https://github.com/Livshitz/shraga/discussions).
105
+ Bugs and concrete proposals: [Issues](https://github.com/Livshitz/shraga/issues).
@@ -67,7 +67,7 @@ summary: |
67
67
  tools_used:
68
68
  - stripe.get_disputes
69
69
  - stripe.get_charges_by_id
70
- - mcp-slack.post_slack_message
70
+ - mcp-slack-use.post_slack_message
71
71
  tool_call_count: 15
72
72
  tool_failures:
73
73
  - tool: stripe.get_disputes
@@ -19,7 +19,7 @@ if (argv[0] === '--uid') {
19
19
  }
20
20
  const prompt =
21
21
  argv.join(' ').trim() ||
22
- 'Call mcp-slack tool get_slack_channels with limit=2 only. Output the JSON ok field and first channel name, nothing else.';
22
+ 'Call mcp-slack-use tool get_slack_channels with limit=2 only. Output the JSON ok field and first channel name, nothing else.';
23
23
 
24
24
  if (!process.env.ANTHROPIC_API_KEY?.trim()) {
25
25
  console.error('Missing ANTHROPIC_API_KEY โ€” cannot run Claude Agent SDK.');
@@ -12,7 +12,7 @@ Dedup applies only when **you** decide to reach out (not when fulfilling a user
12
12
 
13
13
  1. **Read the comms log** โ€” `Read data/comms-log.jsonl` (tail last 50-100 lines if large). If the file exceeds 500 lines, only read the last 100.
14
14
  2. **Check for recent similar outreach** โ€” same recipient + similar topic within the last 24h = skip or significantly alter the message
15
- 3. **For Slack** โ€” the mcp-slack skill already requires reading channel history before posting (rule #6). That check + this log together cover both same-session and cross-session duplicates.
15
+ 3. **For Slack** โ€” the mcp-slack-use skill already requires reading channel history before posting (rule #6). That check + this log together cover both same-session and cross-session duplicates.
16
16
 
17
17
  ### What counts as a duplicate (proactive only)
18
18
 
@@ -83,7 +83,7 @@ Print a summary table:
83
83
 
84
84
  ```
85
85
  MCP Sync Results:
86
- mcp-slack up to date
86
+ mcp-slack-use up to date
87
87
  mcp-firebase pulled 3 commits
88
88
  mcp-pdf cloned (NEW)
89
89
  mcp-cursor pushed 1, pulled 2
@@ -92,7 +92,7 @@ Missing env vars (add to .env):
92
92
  mcp-stripe STRIPE_SECRET_KEY
93
93
  mcp-gmail GOOGLE_SERVICE_ACCOUNT, GMAIL_USER_EMAIL
94
94
 
95
- All configured: mcp-slack, mcp-firebase, mcp-google-drive
95
+ All configured: mcp-slack-use, mcp-firebase, mcp-google-drive
96
96
  ```
97
97
 
98
98
  ## Rules
@@ -85,7 +85,7 @@ List checkpoints with `git -C "$APP_DIR" log --oneline`; reset to any of them.
85
85
  - `src/server/mcp.ts` โ€” MCP server injection and config
86
86
  - `src/server/skills.ts` โ€” skill loading and injection
87
87
  - `defaults/` โ€” canonical source for runtime-seeded dirs (see Architecture skill ยง "Defaults โ†’ Runtime Seeding Pattern")
88
- - `vendor/` โ€” vendored MCP servers (mcp-firebase, mcp-github, mcp-slack, etc.)
88
+ - `vendor/` โ€” vendored MCP servers (mcp-firebase, mcp-github, mcp-slack-use, etc.)
89
89
  - `data/` โ€” runtime data, git-tracked behavioral config via data-sync (skills, mcps, workspace, schedules, contacts, agent-config, whitelist)
90
90
 
91
91
  ## Where one-off / custom code goes (NOT main `src/`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shraga",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "The teammate you delegate coding to โ€” a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -63,7 +63,7 @@
63
63
  "express": "^4.21.1",
64
64
  "firebase": "^11.0.0",
65
65
  "lucide-react": "^0.468.0",
66
- "mcp-slack": "github:Livshitz/mcp-slack#7680804b8dfdf85c50ca66ede8931c364e0516ee",
66
+ "mcp-slack-use": "github:Livshitz/mcp-slack-use#1dde291",
67
67
  "react": "^19.0.0",
68
68
  "react-dom": "^19.0.0",
69
69
  "react-markdown": "^9.0.0",
@@ -457,7 +457,7 @@ export class ClaudeCodeEngine implements AgentEngine {
457
457
  }
458
458
 
459
459
  const pending = pendingToolUses.get(String(block.tool_use_id));
460
- if (pending?.tool === 'mcp__mcp-slack__post_slack_message') {
460
+ if (pending?.tool === 'mcp__mcp-slack-use__post_slack_message') {
461
461
  try {
462
462
  const parsed = typeof output === 'string' ? JSON.parse(output) : output;
463
463
  const ts = parsed?.ts || parsed?.preview?.ts;
@@ -470,7 +470,7 @@ export class ClaudeCodeEngine implements AgentEngine {
470
470
  }
471
471
  } catch (err) { console.warn('[claude] Failed to track proactive message:', (err as Error).message); }
472
472
  }
473
- if (pending?.tool === 'mcp__mcp-slack__post_slack_poll') {
473
+ if (pending?.tool === 'mcp__mcp-slack-use__post_slack_poll') {
474
474
  try {
475
475
  const parsed = typeof output === 'string' ? JSON.parse(output) : output;
476
476
  const body = ((pending.input as any)?.body ?? {}) as Record<string, any>;
@@ -62,7 +62,7 @@ const SLACK_MENTION_FIELDS: Record<string, string[]> = {
62
62
  const resolveSlackMentions: HookCallback = async (input) => {
63
63
  if (input.hook_event_name !== 'PreToolUse') return {};
64
64
  const { tool_name, tool_input } = input as PreToolUseHookInput;
65
- const m = /^mcp__mcp-slack__(post_slack_\w+)$/.exec(tool_name);
65
+ const m = /^mcp__mcp-slack-use__(post_slack_\w+)$/.exec(tool_name);
66
66
  if (!m) return {};
67
67
  const fields = SLACK_MENTION_FIELDS[m[1]];
68
68
  if (!fields) return {};
@@ -135,7 +135,7 @@ export function buildHooks(): Partial<Record<HookEvent, HookCallbackMatcher[]>>
135
135
  return {
136
136
  PreToolUse: [
137
137
  { matcher: 'Bash', hooks: [forceBackgroundForScripts] },
138
- { matcher: 'mcp__mcp-slack__post_slack_.*', hooks: [resolveSlackMentions] },
138
+ { matcher: 'mcp__mcp-slack-use__post_slack_.*', hooks: [resolveSlackMentions] },
139
139
  { matcher: 'mcp__mcp-firebase-(?:prod|lab)__get_db.*', hooks: [guardFirebaseReads] },
140
140
  ],
141
141
  };
@@ -1,4 +1,4 @@
1
- // Proactive Slack poll / directed-question state. The vendor mcp-slack `post_slack_poll`
1
+ // Proactive Slack poll / directed-question state. The mcp-slack-use `post_slack_poll`
2
2
  // tool posts the interactive message; shraga owns everything after: vote state, live
3
3
  // tally updates (chat.update), closing on deadline/quorum/first-answer, and waking the
4
4
  // originating agent session once with the result ("close then report").
@@ -1,11 +1,11 @@
1
- // Slack client โ€” the ONE agent-glue seam onto the mcp-slack package `client` (unified Slack
1
+ // Slack client โ€” the ONE agent-glue seam onto the mcp-slack-use package `client` (unified Slack
2
2
  // Web API, single bot-vs-user token-resolution path). The former duplicate `slackPost` is gone:
3
3
  // everything routes through the package. Only contact/mention resolution stays here โ€” this app owns
4
4
  // the contacts store, so it can't live in the vendor package.
5
5
  import * as contacts from '../contacts.ts';
6
- import { getUserProfile } from 'mcp-slack/src/client.ts';
6
+ import { getUserProfile } from 'mcp-slack-use/src/client.ts';
7
7
 
8
- export * from 'mcp-slack/src/client.ts';
8
+ export * from 'mcp-slack-use/src/client.ts';
9
9
 
10
10
  /** Rewrite Slack `<@Uxxx>` (and bare user ids) to `@Name (operator)?`, learning contacts as it goes. */
11
11
  export async function resolveUserMentions(text: string): Promise<string> {
@@ -1,6 +1,6 @@
1
1
  // Slack agent-glue โ€” the app-side half of the Slack bot. The Slack transport/protocol (routes, HMAC
2
2
  // verify, dedupe, DM identity, file hydrate, streamer wiring, reaction lifecycle) lives in the
3
- // mcp-slack package `ingress`. This module owns only what this app owns: sessions, locks, contacts,
3
+ // mcp-slack-use package `ingress`. This module owns only what this app owns: sessions, locks, contacts,
4
4
  // thread-context sync, artifacts, broadcast, and message persistence โ€” surfaced to the ingress as
5
5
  // callbacks (shouldRespond / onMessage / onReplied) plus the crash-recovery resume path.
6
6
  import crypto from 'node:crypto';
@@ -17,7 +17,7 @@ import {
17
17
  getChannelName, getUserName, getUserProfile, resolveUserMentions, isSupportedFile, SUPPORTED_FILE_MIMES,
18
18
  downloadSlackFileBuffer,
19
19
  } from './api.ts';
20
- import { pipeAgentReply, type AgentEvent, type IngressMessage } from 'mcp-slack/src/ingress.ts';
20
+ import { pipeAgentReply, type AgentEvent, type IngressMessage } from 'mcp-slack-use/src/ingress.ts';
21
21
  import { makeSlackQuestionHandler } from './questions.ts';
22
22
  import * as contacts from '../contacts.ts';
23
23
  import { getChannelContext, invalidateChannelContext } from './context-cache.ts';
@@ -189,7 +189,7 @@ export async function* runAgentTurn(msg: IngressMessage): AsyncGenerator<AgentEv
189
189
  : `#${channelName || 'channel'} context`;
190
190
  appendMessage(sessionId, {
191
191
  id: crypto.randomUUID(), role: 'user',
192
- blocks: [{ type: 'context', label, text: `${channelCtx}\n\n[Use mcp-slack tools (get_slack_history_by_channel, post_slack_message) for further context or replies. Always read channel history before posting.]` }],
192
+ blocks: [{ type: 'context', label, text: `${channelCtx}\n\n[Use mcp-slack-use tools (get_slack_history_by_channel, post_slack_message) for further context or replies. Always read channel history before posting.]` }],
193
193
  });
194
194
  }
195
195
  }
@@ -1,8 +1,8 @@
1
- // slackFeature โ€” the ServerFeature that mounts Slack in this app. It wires the mcp-slack package
1
+ // slackFeature โ€” the ServerFeature that mounts Slack in this app. It wires the mcp-slack-use package
2
2
  // `ingress` (protocol) to the agent-glue (bot.ts) and subscribes the data-sync deploy notifier
3
3
  // (event bus โ†’ owner DMs). Slack ships in this app, so index.ts registers this directly.
4
4
  import type { ServerFeature, FeatureContext } from '../features.ts';
5
- import { registerSlackIngress } from 'mcp-slack/src/ingress.ts';
5
+ import { registerSlackIngress } from 'mcp-slack-use/src/ingress.ts';
6
6
  import { subscribeEvents } from '../events/bus.ts';
7
7
  import { postMessage, resolveUserMentions } from './api.ts';
8
8
  import { runAgentTurn, shouldRespond, onReplied, retrySlackSession, setBroadcast } from './bot.ts';
@@ -27,6 +27,13 @@ export function registerTurnContext(name: string, contribute: TurnContextContrib
27
27
  contributors.set(name, contribute);
28
28
  }
29
29
 
30
+ /** Clear all registered contributors. For tests: the registry is a process-global, so a test that
31
+ * asserts the empty-registry (no-contributor) behavior must reset it first or it inherits whatever
32
+ * another test file registered โ€” bun's cross-file order is not stable across platforms. */
33
+ export function clearTurnContext(): void {
34
+ contributors.clear();
35
+ }
36
+
30
37
  /**
31
38
  * Collect every contributor's block for this turn, joined by a blank line. Empty when none is
32
39
  * registered โ€” which is the core's own state, so the core's prompt is byte-identical to before.