shraga 0.1.13 → 0.1.14
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
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
|
"main": "./src/index.ts",
|
|
@@ -13,8 +13,7 @@ import { resolveModelSwitch } from '../model-aliases.ts';
|
|
|
13
13
|
import type { WsEvent, AskQuestion, QuestionAnswers, QuestionHandler } from '../claude.ts';
|
|
14
14
|
import type { AgentEngine, EngineStreamOpts, EngineModel } from './types.ts';
|
|
15
15
|
import { getPromptSuffix } from '../prompt-suffix.ts';
|
|
16
|
-
|
|
17
|
-
const PROJECT_ROOT = path.resolve(import.meta.dirname, '..', '..', '..');
|
|
16
|
+
import { APP_ROOT } from '../paths.ts';
|
|
18
17
|
const IMMUTABLE_SYSTEM_PROMPT = readFileSync(path.resolve(import.meta.dirname, '../../../defaults/system-prompt.md'), 'utf-8');
|
|
19
18
|
const DEFAULT_USER_PROMPT = `You are a helpful assistant with access to MCP tools.`;
|
|
20
19
|
const DEFAULT_ALLOWED_TOOLS = ['Read', 'Edit', 'Bash', 'WebSearch', 'Glob', 'LS', 'ToolSearch'];
|
|
@@ -175,7 +174,7 @@ export class ClaudeCodeEngine implements AgentEngine {
|
|
|
175
174
|
|
|
176
175
|
async *stream(opts: EngineStreamOpts): AsyncGenerator<WsEvent> {
|
|
177
176
|
const { config, directives } = opts;
|
|
178
|
-
const cwd =
|
|
177
|
+
const cwd = APP_ROOT;
|
|
179
178
|
|
|
180
179
|
const fullPrompt = buildHistoryPrompt(opts.conversation, opts.contextBlock, opts.prompt);
|
|
181
180
|
const permMode = opts.onPermissionRequest ? 'default' : (config.permissionMode ?? 'acceptEdits');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { getHttpSidecarSpecs, type HttpSidecarSpec } from './shraga-config.ts';
|
|
4
|
+
import { APP_ROOT } from './paths.ts';
|
|
4
5
|
|
|
5
|
-
const PROJECT_ROOT = path.resolve(import.meta.dirname, '..', '..');
|
|
6
6
|
const sidecars = new Map<string, { proc: ChildProcess; spec: HttpSidecarSpec }>();
|
|
7
7
|
|
|
8
8
|
async function isPortAlive(url: string): Promise<boolean> {
|
|
@@ -17,7 +17,7 @@ async function isPortAlive(url: string): Promise<boolean> {
|
|
|
17
17
|
let shuttingDown = false;
|
|
18
18
|
|
|
19
19
|
function startOne(spec: HttpSidecarSpec, restarts = 0) {
|
|
20
|
-
const vendorDir = path.join(
|
|
20
|
+
const vendorDir = path.join(APP_ROOT, 'vendor', spec.dir);
|
|
21
21
|
const entrypoint = path.join(vendorDir, 'src/mcp/cli.ts');
|
|
22
22
|
const args = ['run', entrypoint, '--port', String(spec.port)];
|
|
23
23
|
const startedAt = Date.now();
|
package/src/server/mcp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { dataPath } from './paths.ts';
|
|
3
|
+
import { dataPath, APP_ROOT } from './paths.ts';
|
|
4
4
|
import { dataSync } from './data-sync.ts';
|
|
5
5
|
import { getGlobalMcpsFromConfig } from './shraga-config.ts';
|
|
6
6
|
|
|
@@ -151,8 +151,6 @@ function withStdioType(config: McpConfig): McpConfig {
|
|
|
151
151
|
return out;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
const PROJECT_ROOT = path.resolve(import.meta.dirname, '..', '..');
|
|
155
|
-
|
|
156
154
|
/** Canonical path baked into MCP env when prod has the file deployed (cwd = app dir). */
|
|
157
155
|
const GOOGLE_SA_DEPLOY_REL = './secrets/google-service-account.json';
|
|
158
156
|
|
|
@@ -163,7 +161,7 @@ const GOOGLE_SA_DEPLOY_REL = './secrets/google-service-account.json';
|
|
|
163
161
|
*/
|
|
164
162
|
function finalizeGoogleServiceAccountCredentials(config: McpConfig): McpConfig {
|
|
165
163
|
const jsonFromEnv = process.env.GOOGLE_SERVICE_ACCOUNT_JSON?.trim();
|
|
166
|
-
const defaultAbs = path.join(
|
|
164
|
+
const defaultAbs = path.join(APP_ROOT, 'secrets/google-service-account.json');
|
|
167
165
|
const defaultExists = existsSync(defaultAbs);
|
|
168
166
|
|
|
169
167
|
let result = { ...config };
|
|
@@ -184,7 +182,7 @@ function finalizeGoogleServiceAccountCredentials(config: McpConfig): McpConfig {
|
|
|
184
182
|
const pathOk =
|
|
185
183
|
raw &&
|
|
186
184
|
!raw.includes('${') &&
|
|
187
|
-
existsSync(path.isAbsolute(raw) ? raw : path.resolve(
|
|
185
|
+
existsSync(path.isAbsolute(raw) ? raw : path.resolve(APP_ROOT, raw.replace(/^\.\//, '')));
|
|
188
186
|
|
|
189
187
|
if (pathOk) continue;
|
|
190
188
|
if (!defaultExists) continue;
|
package/src/server/paths.ts
CHANGED
|
@@ -2,7 +2,11 @@ import path from 'node:path';
|
|
|
2
2
|
import { readdirSync } from 'node:fs';
|
|
3
3
|
|
|
4
4
|
function resolveDataDir(): string {
|
|
5
|
-
|
|
5
|
+
// Absolutize against cwd: run.sh sets a RELATIVE `DATA_DIR=data-<env>`, and a relative path breaks
|
|
6
|
+
// any consumer that isn't cwd-relative — notably the dynamic `import(configPath)` in
|
|
7
|
+
// shraga-config.ts, which resolves a relative specifier against the IMPORTING MODULE
|
|
8
|
+
// (`node_modules/shraga/src/server/`), not the process cwd.
|
|
9
|
+
if (process.env.DATA_DIR) return path.resolve(process.env.DATA_DIR);
|
|
6
10
|
const root = process.cwd();
|
|
7
11
|
const hasNamed = readdirSync(root).some(f => f.startsWith('data-'));
|
|
8
12
|
if (hasNamed) {
|
|
@@ -17,8 +21,55 @@ function resolveDataDir(): string {
|
|
|
17
21
|
export const DATA_DIR = resolveDataDir();
|
|
18
22
|
export const dataPath = (...segments: string[]) => path.join(DATA_DIR, ...segments);
|
|
19
23
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// `
|
|
24
|
-
|
|
24
|
+
// ── Two distinct roots. Conflating them is what broke npm-consumer deployments. ──────────────────
|
|
25
|
+
//
|
|
26
|
+
// PACKAGE_ROOT — where the SHRAGA PACKAGE's own shipped assets live (`defaults/`, `dist/client/`,
|
|
27
|
+
// `package.json` — everything in package.json `files`). Package-relative is CORRECT here: in an npm
|
|
28
|
+
// consumer these really do live under `node_modules/shraga/`. Do not "fix" this to APP_ROOT.
|
|
29
|
+
export const PACKAGE_ROOT = path.resolve(import.meta.dirname, '..', '..');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* APP_ROOT — the DEPLOYMENT/consumer root: where `vendor/`, `secrets/` and `data/` live, and where
|
|
33
|
+
* the agent's project filesystem is rooted. NOT shipped in the package.
|
|
34
|
+
*
|
|
35
|
+
* In a source checkout this equals PACKAGE_ROOT. In an npm consumer (`shraga-circles`, `shraga-ee`)
|
|
36
|
+
* it is the CONSUMER root, while PACKAGE_ROOT is `<consumer>/node_modules/shraga` — resolving vendor
|
|
37
|
+
* from PACKAGE_ROOT is what silently killed ~21/25 MCPs in prod.
|
|
38
|
+
*
|
|
39
|
+
* Signal, in precedence order:
|
|
40
|
+
* 1. SHRAGA_APP_ROOT env — explicit escape hatch for any layout the heuristics get wrong.
|
|
41
|
+
* 2. node_modules ancestor — if this file sits under `.../node_modules/shraga/...`, the app root is
|
|
42
|
+
* the directory CONTAINING that `node_modules`. Independent of cwd, so it survives a server
|
|
43
|
+
* started from anywhere (systemd, a cron shell, `bun --cwd`).
|
|
44
|
+
* 3. process.cwd() — the source-checkout case, and already this module's established app-root signal
|
|
45
|
+
* (see resolveDataDir above; run.sh `cd`s to the app root before launching).
|
|
46
|
+
*
|
|
47
|
+
* Failure modes, explicit:
|
|
48
|
+
* - Hoisted/pnpm layouts where `shraga` resolves to a store dir outside the consumer's own
|
|
49
|
+
* node_modules: rule 2 picks the hoisting root, which may not be the dir holding `vendor/`.
|
|
50
|
+
* - A nested `node_modules/x/node_modules/shraga`: rule 2 stops at the INNERMOST node_modules.
|
|
51
|
+
* Both are exactly why rule 1 exists — set SHRAGA_APP_ROOT and the heuristics are bypassed.
|
|
52
|
+
*/
|
|
53
|
+
function resolveAppRoot(): string {
|
|
54
|
+
const explicit = process.env.SHRAGA_APP_ROOT?.trim();
|
|
55
|
+
if (explicit) return path.resolve(explicit);
|
|
56
|
+
|
|
57
|
+
const marker = `${path.sep}node_modules${path.sep}`;
|
|
58
|
+
const idx = PACKAGE_ROOT.lastIndexOf(marker);
|
|
59
|
+
if (idx !== -1) return PACKAGE_ROOT.slice(0, idx);
|
|
60
|
+
|
|
61
|
+
return process.cwd();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const APP_ROOT = resolveAppRoot();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @deprecated Ambiguous name — use APP_ROOT (vendor/secrets/data, agent cwd) or PACKAGE_ROOT
|
|
68
|
+
* (shipped assets) explicitly.
|
|
69
|
+
*
|
|
70
|
+
* Aliased to APP_ROOT, not PACKAGE_ROOT, deliberately: every remaining external consumer of this
|
|
71
|
+
* export (shraga-ee `engine/cursor.ts`, `engine/agentx.ts`) uses it as the agent's project root —
|
|
72
|
+
* i.e. they meant APP_ROOT and were hitting the same npm-layout bug. Pointing the alias here fixes
|
|
73
|
+
* them without an EE change. Nothing in this package reads shipped assets through it.
|
|
74
|
+
*/
|
|
75
|
+
export const PROJECT_ROOT = APP_ROOT;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { DATA_DIR } from './paths.ts';
|
|
3
|
+
import { DATA_DIR, APP_ROOT } from './paths.ts';
|
|
4
4
|
import type { McpServerConfig, McpConfig, McpHttpServerConfig } from './mcp.ts';
|
|
5
5
|
|
|
6
6
|
/** Shorthand for vendor-dir MCPs (auto-resolves command/args from vendor/{name}) */
|
|
@@ -70,8 +70,6 @@ export function defineConfig(config: ShragaConfig): ShragaConfig {
|
|
|
70
70
|
return config;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const PROJECT_ROOT = path.resolve(import.meta.dirname, '..', '..');
|
|
74
|
-
|
|
75
73
|
/**
|
|
76
74
|
* Config filenames, in precedence order. `shraga.config.ts` is canonical; `unclaw.config.ts` is
|
|
77
75
|
* the legacy name kept for back-compat — existing deployments have that file in their data dir,
|
|
@@ -131,7 +129,7 @@ export function getGlobalMcpsFromConfig(): McpConfig {
|
|
|
131
129
|
result[name] = { type: 'stdio', ...full } satisfies McpServerConfig;
|
|
132
130
|
} else {
|
|
133
131
|
const shorthand = entry as McpShorthandEntry;
|
|
134
|
-
const vendorDir = path.join(
|
|
132
|
+
const vendorDir = path.join(APP_ROOT, 'vendor', shorthand.dir ?? name);
|
|
135
133
|
const command = shorthand.command ?? 'bun';
|
|
136
134
|
const args = shorthand.args ?? ['run', path.join(vendorDir, 'src/mcp/cli.ts'), '--stdio'];
|
|
137
135
|
const env: Record<string, string> = {};
|
package/src/server/skills.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { mkdirSync, readdirSync, readFileSync, writeFileSync, existsSync, unlinkSync, renameSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { DATA_DIR, dataPath } from './paths.ts';
|
|
3
|
+
import { DATA_DIR, dataPath, APP_ROOT } from './paths.ts';
|
|
4
4
|
import { getBuiltinSkillNames } from './seed.ts';
|
|
5
5
|
import { dataSync } from './data-sync.ts';
|
|
6
6
|
import { injectFile } from './file-inject.ts';
|
|
7
7
|
import { getGlobalMcpConfig } from './mcp.ts';
|
|
8
8
|
|
|
9
|
-
const PROJECT_ROOT = path.resolve(import.meta.dirname, '..', '..');
|
|
10
|
-
|
|
11
9
|
const SKILLS_DIR = dataPath('skills');
|
|
12
10
|
const DEFAULTS_PATH = dataPath('skills-defaults.json');
|
|
13
11
|
|
|
@@ -108,7 +106,7 @@ function formatMcpCommandBlock(mcpName: string, skillBody: string, args: string)
|
|
|
108
106
|
* Markdown from `vendor/<serverName>/.claude/skills/<serverName>/SKILL.md` — same file the MCP exposes as skill://serverName/workflow.
|
|
109
107
|
*/
|
|
110
108
|
export function resolveMcpBundledSkillContent(serverName: string): string | null {
|
|
111
|
-
const file = path.join(
|
|
109
|
+
const file = path.join(APP_ROOT, 'vendor', serverName, '.claude/skills', serverName, 'SKILL.md');
|
|
112
110
|
if (!existsSync(file)) return null;
|
|
113
111
|
return readFileSync(file, 'utf-8');
|
|
114
112
|
}
|
|
@@ -128,7 +126,7 @@ export function resolvedSkillInjectionBlock(name: string): string | null {
|
|
|
128
126
|
}
|
|
129
127
|
|
|
130
128
|
function mcpSkillFilePath(serverName: string): string {
|
|
131
|
-
return path.join(
|
|
129
|
+
return path.join(APP_ROOT, 'vendor', serverName, '.claude/skills', serverName, 'SKILL.md');
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
/**
|