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 +46 -50
- package/defaults/agents/trace-extractor.md +1 -1
- package/defaults/scripts/agent-once.ts +1 -1
- package/defaults/skills/communications.md +1 -1
- package/defaults/skills/mcps-sync.md +2 -2
- package/defaults/skills/self-aware.md +1 -1
- package/package.json +2 -2
- package/src/server/engine/claude-code.ts +2 -2
- package/src/server/hooks.ts +2 -2
- package/src/server/polls.ts +1 -1
- package/src/server/slack/api.ts +3 -3
- package/src/server/slack/bot.ts +3 -3
- package/src/server/slack/feature.ts +2 -2
- package/src/server/turn-context.ts +7 -0
package/README.md
CHANGED
|
@@ -2,21 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
# ๐งโ๐ป Shraga
|
|
4
4
|
|
|
5
|
-
**The teammate you delegate coding to
|
|
5
|
+
**The teammate you delegate coding to. Just forward it to Shraga.**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/shraga)
|
|
8
8
|
[](https://github.com/Livshitz/shraga/actions/workflows/ci.yml)
|
|
9
|
-
[](./LICENSE)
|
|
10
9
|
|
|
11
10
|
</div>
|
|
12
11
|
|
|
13
|
-
Shraga is a self-hostable, multi-user web UI for running an AI coding agent
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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
|
|
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
|
|
36
|
-
|
|
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
|
-
|
|
56
|
+
Sign in and start delegating.
|
|
43
57
|
|
|
44
|
-
|
|
58
|
+
### Auth: subscription or API key
|
|
45
59
|
|
|
46
|
-
|
|
60
|
+
Shraga runs the agent through **Claude Code**, so it uses whatever Claude Code is authenticated
|
|
61
|
+
with:
|
|
47
62
|
|
|
48
|
-
- **
|
|
49
|
-
|
|
50
|
-
- **
|
|
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
|
|
58
|
-
|
|
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` |
|
|
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` |
|
|
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`
|
|
84
|
-
|
|
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
|
|
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/`
|
|
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
|
|
95
|
+
Shraga is a set of ideas before it is a binary:
|
|
97
96
|
|
|
98
|
-
- **[Concept](./docs/concept.md)
|
|
99
|
-
- **[The shared brain](./docs/shared-brain.md)
|
|
100
|
-
|
|
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
|
|
105
|
-
Bugs and concrete proposals
|
|
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).
|
|
@@ -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.
|
|
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#
|
|
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-
|
|
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-
|
|
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>;
|
package/src/server/hooks.ts
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
};
|
package/src/server/polls.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Proactive Slack poll / directed-question state. The
|
|
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").
|
package/src/server/slack/api.ts
CHANGED
|
@@ -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> {
|
package/src/server/slack/bot.ts
CHANGED
|
@@ -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.
|