dreamcontext 0.5.0 → 0.5.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.md +83 -45
- package/dist/index.js +97 -63
- package/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
<a href="#why">Why</a> ·
|
|
17
17
|
<a href="#how-it-works">How It Works</a> ·
|
|
18
18
|
<a href="#quick-start">Quick Start</a> ·
|
|
19
|
+
<a href="#skills">Skills</a> ·
|
|
20
|
+
<a href="#staying-up-to-date">Updating</a> ·
|
|
19
21
|
<a href="#dashboard">Dashboard</a> ·
|
|
20
22
|
<a href="#council">Council</a> ·
|
|
21
23
|
<a href="#memory-recall">Memory Recall</a> ·
|
|
@@ -108,10 +110,10 @@ flowchart LR
|
|
|
108
110
|
## Quick Start
|
|
109
111
|
|
|
110
112
|
```bash
|
|
111
|
-
curl -fsSL https://
|
|
113
|
+
curl -fsSL https://cdn.jsdelivr.net/npm/dreamcontext/install.sh | sh
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
>
|
|
116
|
+
> Served from the published npm package via CDN — works with a private repo, no GitHub access needed.
|
|
115
117
|
|
|
116
118
|
**Manual install (npm):**
|
|
117
119
|
|
|
@@ -134,38 +136,6 @@ dreamcontext install-skill --platforms claude,codex
|
|
|
134
136
|
|
|
135
137
|
Two commands. Next session, the hook fires, context loads, and the agent is ready.
|
|
136
138
|
|
|
137
|
-
### Optional Skill Packs
|
|
138
|
-
|
|
139
|
-
Beyond the core context management skill, dreamcontext ships with curated skill packs you can install for your team's workflow:
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
# Browse and install interactively (terminal checkbox UI)
|
|
143
|
-
dreamcontext install-skill --packs
|
|
144
|
-
|
|
145
|
-
# Install specific packs directly
|
|
146
|
-
dreamcontext install-skill --packs engineering design
|
|
147
|
-
|
|
148
|
-
# Install a single sub-skill
|
|
149
|
-
dreamcontext install-skill --skill firebase-firestore
|
|
150
|
-
|
|
151
|
-
# See what's available
|
|
152
|
-
dreamcontext install-skill --list
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
| Pack | What it covers | Sub-skills |
|
|
156
|
-
|------|---------------|------------|
|
|
157
|
-
| **engineering** | Coding standards, security, testing, architecture | backend-principles, web-app-frontend, firebase-cloud-functions, firebase-firestore |
|
|
158
|
-
| **design** | Design systems, typography, colors, accessibility | frontend-principles, design-web, design-mobile, onboarding-design |
|
|
159
|
-
| **growth** | Retention, distribution, monetization, analytics | performance-marketing, lean-analytics-experiments, lean-analytics-metrics |
|
|
160
|
-
| **brand-voice** | Brand enforcement, discovery, guideline generation | discover-brand, guideline-generation |
|
|
161
|
-
| **system-prompts** | Prompt engineering, cognitive architecture, agent design | *(standalone)* |
|
|
162
|
-
|
|
163
|
-
Packs install to platform-specific paths:
|
|
164
|
-
- Claude: `.claude/skills/{pack-name}/` (+ related agents in `.claude/agents/`)
|
|
165
|
-
- Codex: `.agents/skills/{pack-name}/` (+ related agents in `.codex/agents/`)
|
|
166
|
-
|
|
167
|
-
Cross-pack dependencies are warned at install time.
|
|
168
|
-
|
|
169
139
|
### Interactive mode
|
|
170
140
|
|
|
171
141
|
Run `dreamcontext` with no arguments to enter interactive mode with a visual menu for all commands.
|
|
@@ -176,20 +146,22 @@ Run `dreamcontext` with no arguments to enter interactive mode with a visual men
|
|
|
176
146
|
your-project/
|
|
177
147
|
├── _dream_context/ # Structured context (git-tracked)
|
|
178
148
|
│ ├── core/
|
|
179
|
-
│ │ ├── 0.soul.md
|
|
180
|
-
│ │ ├── 1.user.md
|
|
181
|
-
│ │ ├── 2.memory.md
|
|
182
|
-
│ │ ├── 3.
|
|
183
|
-
│ │ ├── 4.tech_stack.md
|
|
149
|
+
│ │ ├── 0.soul.md # Identity, principles, rules
|
|
150
|
+
│ │ ├── 1.user.md # Your preferences, project details
|
|
151
|
+
│ │ ├── 2.memory.md # Decisions & known issues
|
|
152
|
+
│ │ ├── 3.style_guide_and_branding.md
|
|
153
|
+
│ │ ├── 4.tech_stack.md # Tech decisions
|
|
184
154
|
│ │ ├── 5.data_structures.sql
|
|
185
|
-
│ │ ├── 6.system_flow.md
|
|
155
|
+
│ │ ├── 6.system_flow.md # Session lifecycle, data flows
|
|
186
156
|
│ │ ├── CHANGELOG.json
|
|
187
157
|
│ │ ├── RELEASES.json
|
|
188
|
-
│ │ └── features/
|
|
189
|
-
│ ├── knowledge/
|
|
190
|
-
│ │ └── *.md
|
|
191
|
-
│ └── state/
|
|
192
|
-
│
|
|
158
|
+
│ │ └── features/ # Feature PRDs
|
|
159
|
+
│ ├── knowledge/ # Tagged docs (index in snapshot)
|
|
160
|
+
│ │ └── *.md # pinned: true → auto-loaded in full
|
|
161
|
+
│ └── state/ # Active tasks + working state
|
|
162
|
+
│ ├── *.md # Active task files
|
|
163
|
+
│ ├── .sleep.json # Sleep debt, session history
|
|
164
|
+
│ └── .version-check.json # Cached update check (24h)
|
|
193
165
|
│
|
|
194
166
|
├── .claude/
|
|
195
167
|
│ ├── skills/dreamcontext/
|
|
@@ -215,6 +187,69 @@ dreamcontext install-instructions --platforms claude,codex
|
|
|
215
187
|
|
|
216
188
|
This writes managed fenced blocks into `CLAUDE.md` and/or `AGENTS.md` at the project root, preserving existing non-managed content.
|
|
217
189
|
|
|
190
|
+
## Skills
|
|
191
|
+
|
|
192
|
+
The core `dreamcontext` skill (installed by `install-skill`) teaches your agent the context system itself. On top of that, dreamcontext ships **curated skill packs and standalone skills** that give your agent domain expertise — loaded on demand, only when the work calls for it, so they cost nothing the rest of the time.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Browse and install interactively (terminal checkbox UI)
|
|
196
|
+
dreamcontext install-skill --packs
|
|
197
|
+
|
|
198
|
+
# Install specific packs directly
|
|
199
|
+
dreamcontext install-skill --packs engineering design
|
|
200
|
+
|
|
201
|
+
# Install one orchestration pack (council, multi-review, goal-skill)
|
|
202
|
+
dreamcontext install-skill --packs goal-skill
|
|
203
|
+
|
|
204
|
+
# Install a single sub-skill or standalone skill
|
|
205
|
+
dreamcontext install-skill --skill firebase-firestore
|
|
206
|
+
dreamcontext install-skill --skill system-prompts
|
|
207
|
+
|
|
208
|
+
# See everything available
|
|
209
|
+
dreamcontext install-skill --list
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Skill packs** (a base skill + on-demand sub-skills or sub-agents):
|
|
213
|
+
|
|
214
|
+
| Pack | What it covers | Inside |
|
|
215
|
+
|------|---------------|--------|
|
|
216
|
+
| **engineering** _(always-on)_ | Coding standards, security, testing, architecture | backend-principles, web-app-frontend, firebase-cloud-functions, firebase-firestore |
|
|
217
|
+
| **design** _(always-on)_ | Design systems, typography, color, accessibility | frontend-principles, design-web, design-mobile, onboarding-design |
|
|
218
|
+
| **growth** | Retention, distribution, monetization, analytics | performance-marketing, lean-analytics-experiments, lean-analytics-metrics |
|
|
219
|
+
| **brand-voice** | Brand enforcement, discovery, guideline generation | discover-brand, guideline-generation |
|
|
220
|
+
| **council** | Multi-persona debate for hard decisions | `council-persona`, `council-synthesizer` agents |
|
|
221
|
+
| **multi-review** | Multi-agent code review (router + niche specialists) | `review-router` + security / cloud-functions / frontend / edge-cases agents |
|
|
222
|
+
| **goal-skill** | Sub-agent-orchestrated execution: plan → review → implement → validate | `goal-planner`, `goal-plan-reviewer`, `goal-implementer`, `goal-validator` agents |
|
|
223
|
+
|
|
224
|
+
**Standalone skills** (install individually with `--skill <name>`):
|
|
225
|
+
|
|
226
|
+
| Skill | What it covers |
|
|
227
|
+
|-------|----------------|
|
|
228
|
+
| **business-idea-discovery** | Market selection, trend validation, competitor intel, pain-point mining, MVP scoping |
|
|
229
|
+
| **business-idea-validation** | Demand testing via landing page + waitlist, quick validation loops |
|
|
230
|
+
| **meta-marketing** | Meta / Facebook / Instagram ad campaigns end to end |
|
|
231
|
+
| **system-prompts** | Prompt engineering, cognitive architecture, agent design |
|
|
232
|
+
|
|
233
|
+
_Always-on_ packs apply their base principles to every relevant task; the rest load only when the work matches. Packs install to platform-specific paths — Claude: `.claude/skills/{pack}/` (+ agents in `.claude/agents/`); Codex: `.agents/skills/{pack}/` (+ agents in `.codex/agents/`). Cross-pack dependencies are warned at install time.
|
|
234
|
+
|
|
235
|
+
## Staying Up to Date
|
|
236
|
+
|
|
237
|
+
dreamcontext tells you when a new version ships, and updating is one command. There are two distinct things to update: the **CLI** (the `dreamcontext` binary) and your **project's installed files** (the skill, agents, and hooks copied into `.claude/` or `.agents/`).
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
dreamcontext upgrade # Upgrade the CLI to the latest published version
|
|
241
|
+
dreamcontext upgrade --check # Just print "current: X latest: Y" and exit
|
|
242
|
+
dreamcontext update # Refresh this project's skill/agent/hook files to match the CLI
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Or re-run the one-command installer — it detects an existing `_dream_context/` and updates in place:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
curl -fsSL https://cdn.jsdelivr.net/npm/dreamcontext/install.sh | sh
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**In-session update nudge.** When a newer version is published, your agent sees a single-line nudge at the top of its loaded context — so you find out while you're working, not months later. The version check is deliberately unobtrusive: it runs **at most once every 24 hours**, never during the context-loading hot path (so session start is never slowed or blocked), and fails silent if npm is unreachable. Opt out entirely with `DREAMCONTEXT_VERSION_CHECK=0`.
|
|
252
|
+
|
|
218
253
|
## Dashboard
|
|
219
254
|
|
|
220
255
|
```bash
|
|
@@ -487,6 +522,9 @@ dreamcontext hook pre-compact # PreCompact hook: save state before co
|
|
|
487
522
|
dreamcontext snapshot # Snapshot only (no hook processing)
|
|
488
523
|
dreamcontext snapshot --tokens # Estimated token count
|
|
489
524
|
dreamcontext doctor # Validate structure
|
|
525
|
+
dreamcontext upgrade # Upgrade the CLI to the latest published version
|
|
526
|
+
dreamcontext upgrade --check # Print current vs latest version, no install
|
|
527
|
+
dreamcontext update # Refresh installed skill/agent/hook files to match the CLI
|
|
490
528
|
dreamcontext install-skill # Install core integration for selected platforms
|
|
491
529
|
dreamcontext install-skill --platforms claude,codex # Explicit platform selection
|
|
492
530
|
dreamcontext install-skill --packs # Interactive skill pack browser
|
package/dist/index.js
CHANGED
|
@@ -1369,8 +1369,8 @@ async function installInstructions(projectRoot, platform, requestedMode) {
|
|
|
1369
1369
|
writeFileSync5(target, block, "utf-8");
|
|
1370
1370
|
return { action: "replaced", target, platform, backup };
|
|
1371
1371
|
}
|
|
1372
|
-
const
|
|
1373
|
-
writeFileSync5(target, existing +
|
|
1372
|
+
const sep6 = existing.endsWith("\n") ? "\n" : "\n\n";
|
|
1373
|
+
writeFileSync5(target, existing + sep6 + block, "utf-8");
|
|
1374
1374
|
return { action: "appended", target, platform };
|
|
1375
1375
|
}
|
|
1376
1376
|
async function installClaudeMd(projectRoot, requestedMode) {
|
|
@@ -6481,21 +6481,10 @@ function registerHookCommand(program) {
|
|
|
6481
6481
|
try {
|
|
6482
6482
|
const prompt = String(input9.prompt ?? "");
|
|
6483
6483
|
if (prompt.trim().length >= 8) {
|
|
6484
|
-
const
|
|
6485
|
-
const skillsRoot = join25(projectRoot, ".claude", "skills");
|
|
6484
|
+
const skillsRoot = join25(process.cwd(), ".claude", "skills");
|
|
6486
6485
|
const docs = loadSkillDocs(skillsRoot);
|
|
6487
|
-
if (docs.length > 0) {
|
|
6488
|
-
|
|
6489
|
-
if (hits.length > 0) {
|
|
6490
|
-
const lines = ["", `\u2014 Related skills (top ${hits.length}) \u2014`];
|
|
6491
|
-
lines.push(" Invoke these via the Skill tool BEFORE acting if they fit the task:");
|
|
6492
|
-
for (const h of hits) {
|
|
6493
|
-
const desc = h.doc.description.length > 120 ? h.doc.description.slice(0, 120) + "\u2026" : h.doc.description;
|
|
6494
|
-
lines.push(` \u2022 ${h.doc.slug}${desc ? ` \u2014 ${desc}` : ""}`);
|
|
6495
|
-
}
|
|
6496
|
-
console.log(lines.join("\n"));
|
|
6497
|
-
gatedSkills = true;
|
|
6498
|
-
}
|
|
6486
|
+
if (docs.length > 0 && bm25Search(prompt, docs, 5).some((h) => h.score >= SKILL_SCORE_THRESHOLD)) {
|
|
6487
|
+
gatedSkills = true;
|
|
6499
6488
|
}
|
|
6500
6489
|
}
|
|
6501
6490
|
} catch (skillErr) {
|
|
@@ -6504,9 +6493,11 @@ function registerHookCommand(program) {
|
|
|
6504
6493
|
}
|
|
6505
6494
|
if (hadRecallHits || gatedSkills) {
|
|
6506
6495
|
const g = ["", "\u26D4 Before you act, get the full picture from project memory \u2014 not optional:"];
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6496
|
+
if (hadRecallHits) {
|
|
6497
|
+
g.push(" \u2022 READ the related knowledge/feature file(s) recalled above in full (Read tool) \u2014 plus anything relevant in your knowledge index. The source code will NOT show the decisions and constraints they hold.");
|
|
6498
|
+
}
|
|
6499
|
+
if (process.env.DREAMCONTEXT_SKILLS_HOOK !== "0") {
|
|
6500
|
+
g.push(" \u2022 REVIEW the skills available to you (the full list is already in your context) and INVOKE any that fit this task via the Skill tool \u2014 do NOT limit yourself to a pre-selected few; scan them all and decide.");
|
|
6510
6501
|
}
|
|
6511
6502
|
g.push(' \u2022 For depth, RECALL more: dreamcontext memory recall "<your keywords>" [--types knowledge,feature,task,memory] \u2014 try a few keyword sets and read the relevant hits in full.');
|
|
6512
6503
|
g.push(' \u2022 CHECK whether a task already exists for this work: dreamcontext memory recall "<keywords>" --types task (or look in _dream_context/state/). If one exists, follow it; if the work is untracked and non-trivial, create one.');
|
|
@@ -6564,7 +6555,7 @@ function registerHookCommand(program) {
|
|
|
6564
6555
|
writeSleepState(root, state);
|
|
6565
6556
|
});
|
|
6566
6557
|
}
|
|
6567
|
-
var MAX_TRANSCRIPT_BYTES, SKILL_SCORE_THRESHOLD,
|
|
6558
|
+
var MAX_TRANSCRIPT_BYTES, SKILL_SCORE_THRESHOLD, ZERO_ANALYSIS, JS_TS_EXTENSIONS, MAX_WALK_LEVELS, BIOME_CONFIGS, PRETTIER_CONFIGS;
|
|
6568
6559
|
var init_hook = __esm({
|
|
6569
6560
|
"src/cli/commands/hook.ts"() {
|
|
6570
6561
|
"use strict";
|
|
@@ -6579,7 +6570,6 @@ var init_hook = __esm({
|
|
|
6579
6570
|
init_install_skill();
|
|
6580
6571
|
MAX_TRANSCRIPT_BYTES = 50 * 1024 * 1024;
|
|
6581
6572
|
SKILL_SCORE_THRESHOLD = 1;
|
|
6582
|
-
MAX_RELATED_SKILLS = 3;
|
|
6583
6573
|
ZERO_ANALYSIS = { changeCount: 0, toolCount: 0, taskSlugs: [] };
|
|
6584
6574
|
JS_TS_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"]);
|
|
6585
6575
|
MAX_WALK_LEVELS = 10;
|
|
@@ -6826,49 +6816,59 @@ var init_router = __esm({
|
|
|
6826
6816
|
|
|
6827
6817
|
// src/server/middleware.ts
|
|
6828
6818
|
async function parseJsonBody(req) {
|
|
6829
|
-
return new Promise((
|
|
6819
|
+
return new Promise((resolve10) => {
|
|
6830
6820
|
const chunks = [];
|
|
6831
6821
|
let size = 0;
|
|
6832
6822
|
req.on("data", (chunk) => {
|
|
6833
6823
|
size += chunk.length;
|
|
6834
6824
|
if (size > MAX_BODY_SIZE) {
|
|
6835
6825
|
req.destroy();
|
|
6836
|
-
|
|
6826
|
+
resolve10(null);
|
|
6837
6827
|
return;
|
|
6838
6828
|
}
|
|
6839
6829
|
chunks.push(chunk);
|
|
6840
6830
|
});
|
|
6841
6831
|
req.on("end", () => {
|
|
6842
6832
|
if (chunks.length === 0) {
|
|
6843
|
-
|
|
6833
|
+
resolve10(null);
|
|
6844
6834
|
return;
|
|
6845
6835
|
}
|
|
6846
6836
|
try {
|
|
6847
6837
|
const body = Buffer.concat(chunks).toString("utf-8");
|
|
6848
|
-
|
|
6838
|
+
resolve10(JSON.parse(body));
|
|
6849
6839
|
} catch {
|
|
6850
|
-
|
|
6840
|
+
resolve10(null);
|
|
6851
6841
|
}
|
|
6852
6842
|
});
|
|
6853
|
-
req.on("error", () =>
|
|
6843
|
+
req.on("error", () => resolve10(null));
|
|
6854
6844
|
});
|
|
6855
6845
|
}
|
|
6856
6846
|
function sendJson(res, statusCode, data) {
|
|
6857
6847
|
const body = JSON.stringify(data);
|
|
6858
6848
|
res.writeHead(statusCode, {
|
|
6859
6849
|
"Content-Type": "application/json",
|
|
6860
|
-
"Content-Length": Buffer.byteLength(body)
|
|
6861
|
-
"Access-Control-Allow-Origin": "*"
|
|
6850
|
+
"Content-Length": Buffer.byteLength(body)
|
|
6862
6851
|
});
|
|
6863
6852
|
res.end(body);
|
|
6864
6853
|
}
|
|
6865
6854
|
function sendError(res, statusCode, error2, message) {
|
|
6866
6855
|
sendJson(res, statusCode, { error: error2, message });
|
|
6867
6856
|
}
|
|
6857
|
+
function isCrossSiteWrite(req) {
|
|
6858
|
+
const method = (req.method || "GET").toUpperCase();
|
|
6859
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
6860
|
+
const origin = req.headers.origin;
|
|
6861
|
+
if (!origin) return false;
|
|
6862
|
+
return !LOCAL_ORIGIN_RE.test(origin);
|
|
6863
|
+
}
|
|
6868
6864
|
function handleCors(req, res) {
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6865
|
+
const origin = req.headers.origin;
|
|
6866
|
+
if (origin && LOCAL_ORIGIN_RE.test(origin)) {
|
|
6867
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
6868
|
+
res.setHeader("Vary", "Origin");
|
|
6869
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
6870
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
6871
|
+
}
|
|
6872
6872
|
if (req.method === "OPTIONS") {
|
|
6873
6873
|
res.writeHead(204);
|
|
6874
6874
|
res.end();
|
|
@@ -6876,11 +6876,12 @@ function handleCors(req, res) {
|
|
|
6876
6876
|
}
|
|
6877
6877
|
return false;
|
|
6878
6878
|
}
|
|
6879
|
-
var MAX_BODY_SIZE;
|
|
6879
|
+
var MAX_BODY_SIZE, LOCAL_ORIGIN_RE;
|
|
6880
6880
|
var init_middleware = __esm({
|
|
6881
6881
|
"src/server/middleware.ts"() {
|
|
6882
6882
|
"use strict";
|
|
6883
6883
|
MAX_BODY_SIZE = 1048576;
|
|
6884
|
+
LOCAL_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i;
|
|
6884
6885
|
}
|
|
6885
6886
|
});
|
|
6886
6887
|
|
|
@@ -7480,6 +7481,21 @@ var init_sleep2 = __esm({
|
|
|
7480
7481
|
}
|
|
7481
7482
|
});
|
|
7482
7483
|
|
|
7484
|
+
// src/server/safe-path.ts
|
|
7485
|
+
import { resolve as resolve4, sep as sep2 } from "path";
|
|
7486
|
+
function safeChildPath(baseDir, child) {
|
|
7487
|
+
if (!child || child.includes("\0")) return null;
|
|
7488
|
+
const base = resolve4(baseDir);
|
|
7489
|
+
const target = resolve4(base, child);
|
|
7490
|
+
if (target !== base && !target.startsWith(base + sep2)) return null;
|
|
7491
|
+
return target;
|
|
7492
|
+
}
|
|
7493
|
+
var init_safe_path = __esm({
|
|
7494
|
+
"src/server/safe-path.ts"() {
|
|
7495
|
+
"use strict";
|
|
7496
|
+
}
|
|
7497
|
+
});
|
|
7498
|
+
|
|
7483
7499
|
// src/server/routes/core.ts
|
|
7484
7500
|
import { existsSync as existsSync31, readdirSync as readdirSync8 } from "fs";
|
|
7485
7501
|
import { join as join29 } from "path";
|
|
@@ -7513,7 +7529,11 @@ async function handleCoreList(_req, res, _params, contextRoot) {
|
|
|
7513
7529
|
}
|
|
7514
7530
|
async function handleCoreGet(_req, res, params, contextRoot) {
|
|
7515
7531
|
const { filename } = params;
|
|
7516
|
-
const filePath =
|
|
7532
|
+
const filePath = safeChildPath(getCoreDir(contextRoot), filename);
|
|
7533
|
+
if (!filePath) {
|
|
7534
|
+
sendError(res, 400, "invalid_path", "Invalid filename.");
|
|
7535
|
+
return;
|
|
7536
|
+
}
|
|
7517
7537
|
if (!existsSync31(filePath)) {
|
|
7518
7538
|
sendError(res, 404, "not_found", `Core file not found: ${filename}`);
|
|
7519
7539
|
return;
|
|
@@ -7560,7 +7580,11 @@ async function handleCoreGet(_req, res, params, contextRoot) {
|
|
|
7560
7580
|
}
|
|
7561
7581
|
async function handleCoreUpdate(req, res, params, contextRoot) {
|
|
7562
7582
|
const { filename } = params;
|
|
7563
|
-
const filePath =
|
|
7583
|
+
const filePath = safeChildPath(getCoreDir(contextRoot), filename);
|
|
7584
|
+
if (!filePath) {
|
|
7585
|
+
sendError(res, 400, "invalid_path", "Invalid filename.");
|
|
7586
|
+
return;
|
|
7587
|
+
}
|
|
7564
7588
|
if (!existsSync31(filePath)) {
|
|
7565
7589
|
sendError(res, 404, "not_found", `Core file not found: ${filename}`);
|
|
7566
7590
|
return;
|
|
@@ -7619,6 +7643,7 @@ var init_core2 = __esm({
|
|
|
7619
7643
|
init_frontmatter();
|
|
7620
7644
|
init_markdown();
|
|
7621
7645
|
init_middleware();
|
|
7646
|
+
init_safe_path();
|
|
7622
7647
|
init_change_tracker();
|
|
7623
7648
|
}
|
|
7624
7649
|
});
|
|
@@ -8190,7 +8215,7 @@ var init_graph = __esm({
|
|
|
8190
8215
|
|
|
8191
8216
|
// src/server/routes/graph.ts
|
|
8192
8217
|
import { existsSync as existsSync36, readFileSync as readFileSync23 } from "fs";
|
|
8193
|
-
import { normalize, resolve as
|
|
8218
|
+
import { normalize, resolve as resolve5, sep as sep3 } from "path";
|
|
8194
8219
|
async function handleGraphGet(_req, res, _params, contextRoot) {
|
|
8195
8220
|
try {
|
|
8196
8221
|
const graph = buildGraph(contextRoot);
|
|
@@ -8207,9 +8232,9 @@ async function handleGraphContentGet(req, res, _params, contextRoot) {
|
|
|
8207
8232
|
sendError(res, 400, "missing_path", 'Query parameter "path" is required.');
|
|
8208
8233
|
return;
|
|
8209
8234
|
}
|
|
8210
|
-
const absRoot =
|
|
8211
|
-
const absTarget =
|
|
8212
|
-
if (!absTarget.startsWith(absRoot +
|
|
8235
|
+
const absRoot = resolve5(contextRoot);
|
|
8236
|
+
const absTarget = resolve5(absRoot, normalize(rawPath));
|
|
8237
|
+
if (!absTarget.startsWith(absRoot + sep3) && absTarget !== absRoot) {
|
|
8213
8238
|
sendError(res, 400, "invalid_path", "Path escapes context root.");
|
|
8214
8239
|
return;
|
|
8215
8240
|
}
|
|
@@ -8265,7 +8290,7 @@ var init_graph2 = __esm({
|
|
|
8265
8290
|
|
|
8266
8291
|
// src/lib/council.ts
|
|
8267
8292
|
import { existsSync as existsSync37, mkdirSync as mkdirSync9, readFileSync as readFileSync24 } from "fs";
|
|
8268
|
-
import { join as join34, resolve as
|
|
8293
|
+
import { join as join34, resolve as resolve6, sep as sep4 } from "path";
|
|
8269
8294
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
8270
8295
|
function getCouncilDir() {
|
|
8271
8296
|
const root = ensureContextRoot();
|
|
@@ -8283,9 +8308,9 @@ function assertSafeSegment(kind, id) {
|
|
|
8283
8308
|
}
|
|
8284
8309
|
}
|
|
8285
8310
|
function assertWithinCouncil(target) {
|
|
8286
|
-
const council =
|
|
8287
|
-
const resolved =
|
|
8288
|
-
if (resolved !== council && !resolved.startsWith(council +
|
|
8311
|
+
const council = resolve6(getCouncilDir());
|
|
8312
|
+
const resolved = resolve6(target);
|
|
8313
|
+
if (resolved !== council && !resolved.startsWith(council + sep4)) {
|
|
8289
8314
|
throw new Error(`Path escapes council directory: ${target}`);
|
|
8290
8315
|
}
|
|
8291
8316
|
return resolved;
|
|
@@ -8472,7 +8497,7 @@ var init_council = __esm({
|
|
|
8472
8497
|
|
|
8473
8498
|
// src/server/routes/council.ts
|
|
8474
8499
|
import { existsSync as existsSync38 } from "fs";
|
|
8475
|
-
import { join as join35, resolve as
|
|
8500
|
+
import { join as join35, resolve as resolve7, sep as sep5 } from "path";
|
|
8476
8501
|
function getCouncilDir2(contextRoot) {
|
|
8477
8502
|
return join35(contextRoot, "council");
|
|
8478
8503
|
}
|
|
@@ -8483,9 +8508,9 @@ function assertSafeSegment2(id) {
|
|
|
8483
8508
|
return true;
|
|
8484
8509
|
}
|
|
8485
8510
|
function assertWithin(root, target) {
|
|
8486
|
-
const r =
|
|
8487
|
-
const t =
|
|
8488
|
-
if (t !== r && !t.startsWith(r +
|
|
8511
|
+
const r = resolve7(root);
|
|
8512
|
+
const t = resolve7(target);
|
|
8513
|
+
if (t !== r && !t.startsWith(r + sep5)) return null;
|
|
8489
8514
|
return t;
|
|
8490
8515
|
}
|
|
8491
8516
|
function extractNamedSubsection(body, heading) {
|
|
@@ -8673,7 +8698,7 @@ var init_council2 = __esm({
|
|
|
8673
8698
|
|
|
8674
8699
|
// src/server/index.ts
|
|
8675
8700
|
import { createServer } from "http";
|
|
8676
|
-
import { resolve as
|
|
8701
|
+
import { resolve as resolve8 } from "path";
|
|
8677
8702
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
8678
8703
|
import { exec } from "child_process";
|
|
8679
8704
|
function buildRouter() {
|
|
@@ -8710,20 +8735,24 @@ function buildRouter() {
|
|
|
8710
8735
|
}
|
|
8711
8736
|
function getDashboardDir() {
|
|
8712
8737
|
const __dirname7 = fileURLToPath6(new URL(".", import.meta.url));
|
|
8713
|
-
return
|
|
8738
|
+
return resolve8(__dirname7, "dashboard");
|
|
8714
8739
|
}
|
|
8715
8740
|
function openBrowser(url) {
|
|
8716
8741
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
8717
8742
|
exec(`${cmd} ${url}`);
|
|
8718
8743
|
}
|
|
8719
8744
|
function startDashboardServer(options) {
|
|
8720
|
-
const { port, contextRoot, open } = options;
|
|
8745
|
+
const { port, contextRoot, open, host = "127.0.0.1" } = options;
|
|
8721
8746
|
const router = buildRouter();
|
|
8722
8747
|
const dashboardDir = getDashboardDir();
|
|
8723
8748
|
return new Promise((resolvePromise, reject) => {
|
|
8724
8749
|
const server = createServer(async (req, res) => {
|
|
8725
8750
|
try {
|
|
8726
8751
|
if (handleCors(req, res)) return;
|
|
8752
|
+
if (isCrossSiteWrite(req)) {
|
|
8753
|
+
sendError(res, 403, "forbidden", "Cross-site request blocked.");
|
|
8754
|
+
return;
|
|
8755
|
+
}
|
|
8727
8756
|
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
8728
8757
|
const method = req.method || "GET";
|
|
8729
8758
|
if (url.pathname.startsWith("/api/")) {
|
|
@@ -8749,11 +8778,16 @@ function startDashboardServer(options) {
|
|
|
8749
8778
|
}
|
|
8750
8779
|
});
|
|
8751
8780
|
server.setTimeout(3e4);
|
|
8752
|
-
server.listen(port, () => {
|
|
8753
|
-
const
|
|
8781
|
+
server.listen(port, host, () => {
|
|
8782
|
+
const shownHost = host === "127.0.0.1" ? "localhost" : host;
|
|
8783
|
+
const url = `http://${shownHost}:${port}`;
|
|
8754
8784
|
console.log(`
|
|
8755
8785
|
Dashboard: ${url}
|
|
8756
8786
|
`);
|
|
8787
|
+
if (host !== "127.0.0.1") {
|
|
8788
|
+
console.log(` WARNING: bound to ${host} \u2014 the dashboard API is reachable from your network.
|
|
8789
|
+
`);
|
|
8790
|
+
}
|
|
8757
8791
|
console.log(" Press Ctrl+C to stop.\n");
|
|
8758
8792
|
if (open) {
|
|
8759
8793
|
openBrowser(url);
|
|
@@ -8788,13 +8822,13 @@ var init_server = __esm({
|
|
|
8788
8822
|
|
|
8789
8823
|
// src/cli/commands/dashboard.ts
|
|
8790
8824
|
function registerDashboardCommand(program) {
|
|
8791
|
-
program.command("dashboard").description("Open the web dashboard in your browser").option("-p, --port <port>", "Port number", "4173").option("--no-open", "Do not open browser automatically").action(async (opts) => {
|
|
8825
|
+
program.command("dashboard").description("Open the web dashboard in your browser").option("-p, --port <port>", "Port number", "4173").option("--host <host>", "Interface to bind (default loopback). Use 0.0.0.0 to expose on your network.", "127.0.0.1").option("--no-open", "Do not open browser automatically").action(async (opts) => {
|
|
8792
8826
|
const contextRoot = ensureContextRoot();
|
|
8793
8827
|
const port = parseInt(opts.port, 10);
|
|
8794
8828
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
8795
8829
|
throw new Error("Invalid port number. Must be between 1 and 65535.");
|
|
8796
8830
|
}
|
|
8797
|
-
await startDashboardServer({ port, contextRoot, open: opts.open });
|
|
8831
|
+
await startDashboardServer({ port, contextRoot, open: opts.open, host: opts.host });
|
|
8798
8832
|
});
|
|
8799
8833
|
}
|
|
8800
8834
|
var init_dashboard = __esm({
|
|
@@ -10120,14 +10154,14 @@ REINFLUENCE_BIN= # optional override; default uses .venv
|
|
|
10120
10154
|
import { spawn, spawnSync } from "child_process";
|
|
10121
10155
|
import { existsSync as existsSync42, mkdirSync as mkdirSync12, cpSync as cpSync2 } from "fs";
|
|
10122
10156
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
10123
|
-
import { dirname as dirname13, resolve as
|
|
10157
|
+
import { dirname as dirname13, resolve as resolve9, join as join37 } from "path";
|
|
10124
10158
|
function findBundledReinfluence() {
|
|
10125
10159
|
const candidates = [
|
|
10126
10160
|
// Installed npm package: dist/<chunk>.js -> ../tools/reinfluence
|
|
10127
|
-
|
|
10128
|
-
|
|
10161
|
+
resolve9(__dirname6, "..", "tools", "reinfluence"),
|
|
10162
|
+
resolve9(__dirname6, "..", "..", "tools", "reinfluence"),
|
|
10129
10163
|
// Dev tree: src/lib/marketing -> ../../../tools/reinfluence
|
|
10130
|
-
|
|
10164
|
+
resolve9(__dirname6, "..", "..", "..", "tools", "reinfluence")
|
|
10131
10165
|
];
|
|
10132
10166
|
for (const c of candidates) {
|
|
10133
10167
|
if (existsSync42(join37(c, "__main__.py"))) return c;
|
|
@@ -11024,7 +11058,7 @@ function getQueue(accountId) {
|
|
|
11024
11058
|
async function withWriteSlot(accountId, fn) {
|
|
11025
11059
|
const q = getQueue(accountId);
|
|
11026
11060
|
if (q.active >= PER_ACCOUNT_WRITE_CONCURRENCY) {
|
|
11027
|
-
await new Promise((
|
|
11061
|
+
await new Promise((resolve10) => q.waiting.push(resolve10));
|
|
11028
11062
|
}
|
|
11029
11063
|
q.active += 1;
|
|
11030
11064
|
try {
|
|
@@ -14110,13 +14144,13 @@ async function resolveBody(body, bodyFile) {
|
|
|
14110
14144
|
return null;
|
|
14111
14145
|
}
|
|
14112
14146
|
function readStdin3() {
|
|
14113
|
-
return new Promise((
|
|
14147
|
+
return new Promise((resolve10, reject) => {
|
|
14114
14148
|
let data = "";
|
|
14115
14149
|
process.stdin.setEncoding("utf8");
|
|
14116
14150
|
process.stdin.on("data", (chunk) => {
|
|
14117
14151
|
data += chunk;
|
|
14118
14152
|
});
|
|
14119
|
-
process.stdin.on("end", () =>
|
|
14153
|
+
process.stdin.on("end", () => resolve10(data));
|
|
14120
14154
|
process.stdin.on("error", reject);
|
|
14121
14155
|
});
|
|
14122
14156
|
}
|
|
@@ -15761,12 +15795,12 @@ import chalk40 from "chalk";
|
|
|
15761
15795
|
function getBanner() {
|
|
15762
15796
|
const logo = renderBanner();
|
|
15763
15797
|
const title = `${chalk40.bold.cyan("D R E A M")}${chalk40.bold.cyanBright(" C O N T E X T")}`;
|
|
15764
|
-
const
|
|
15798
|
+
const sep6 = chalk40.dim("\u2501".repeat(25));
|
|
15765
15799
|
const tagline = chalk40.dim("persistent memory for AI agents");
|
|
15766
15800
|
const text = [
|
|
15767
15801
|
"",
|
|
15768
15802
|
` ${title}`,
|
|
15769
|
-
` ${
|
|
15803
|
+
` ${sep6}`,
|
|
15770
15804
|
` ${tagline}`
|
|
15771
15805
|
].join("\n");
|
|
15772
15806
|
return "\n" + logo + text + "\n";
|
package/install.sh
CHANGED