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 CHANGED
@@ -16,6 +16,8 @@
16
16
  <a href="#why">Why</a> &nbsp;&middot;&nbsp;
17
17
  <a href="#how-it-works">How It Works</a> &nbsp;&middot;&nbsp;
18
18
  <a href="#quick-start">Quick Start</a> &nbsp;&middot;&nbsp;
19
+ <a href="#skills">Skills</a> &nbsp;&middot;&nbsp;
20
+ <a href="#staying-up-to-date">Updating</a> &nbsp;&middot;&nbsp;
19
21
  <a href="#dashboard">Dashboard</a> &nbsp;&middot;&nbsp;
20
22
  <a href="#council">Council</a> &nbsp;&middot;&nbsp;
21
23
  <a href="#memory-recall">Memory Recall</a> &nbsp;&middot;&nbsp;
@@ -108,10 +110,10 @@ flowchart LR
108
110
  ## Quick Start
109
111
 
110
112
  ```bash
111
- curl -fsSL https://raw.githubusercontent.com/meanllbrl/dreamcontext/main/install.sh | sh
113
+ curl -fsSL https://cdn.jsdelivr.net/npm/dreamcontext/install.sh | sh
112
114
  ```
113
115
 
114
- > (goes live once published to npm & merged to main)
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 # Identity, principles, rules
180
- │ │ ├── 1.user.md # Your preferences, project details
181
- │ │ ├── 2.memory.md # Decisions, issues, learnings
182
- │ │ ├── 3.style_guide.md # Style & branding
183
- │ │ ├── 4.tech_stack.md # Tech decisions
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 # Session lifecycle, data flows
155
+ │ │ ├── 6.system_flow.md # Session lifecycle, data flows
186
156
  │ │ ├── CHANGELOG.json
187
157
  │ │ ├── RELEASES.json
188
- │ │ └── features/ # Feature PRDs
189
- │ ├── knowledge/ # Tagged docs (index in snapshot)
190
- │ │ └── *.md # pinned: true → auto-loaded in full
191
- │ └── state/ # Active tasks, sleep state
192
- └── .sleep.json
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 sep5 = existing.endsWith("\n") ? "\n" : "\n\n";
1373
- writeFileSync5(target, existing + sep5 + block, "utf-8");
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 projectRoot = process.cwd();
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
- const hits = bm25Search(prompt, docs, MAX_RELATED_SKILLS).filter((h) => h.score >= SKILL_SCORE_THRESHOLD);
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
- g.push(" \u2022 READ the related knowledge/feature file(s) for this task in full (Read tool) \u2014 the relevant ones recalled above plus anything in your knowledge index. The source code will NOT show the decisions and constraints they hold.");
6508
- if (gatedSkills) {
6509
- g.push(' \u2022 INVOKE the related skill(s) above via the Skill tool if they fit \u2014 required, not "if you like".');
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, MAX_RELATED_SKILLS, ZERO_ANALYSIS, JS_TS_EXTENSIONS, MAX_WALK_LEVELS, BIOME_CONFIGS, PRETTIER_CONFIGS;
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((resolve9) => {
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
- resolve9(null);
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
- resolve9(null);
6833
+ resolve10(null);
6844
6834
  return;
6845
6835
  }
6846
6836
  try {
6847
6837
  const body = Buffer.concat(chunks).toString("utf-8");
6848
- resolve9(JSON.parse(body));
6838
+ resolve10(JSON.parse(body));
6849
6839
  } catch {
6850
- resolve9(null);
6840
+ resolve10(null);
6851
6841
  }
6852
6842
  });
6853
- req.on("error", () => resolve9(null));
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
- res.setHeader("Access-Control-Allow-Origin", "*");
6870
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
6871
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
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 = join29(getCoreDir(contextRoot), filename);
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 = join29(getCoreDir(contextRoot), filename);
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 resolve4, sep as sep2 } from "path";
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 = resolve4(contextRoot);
8211
- const absTarget = resolve4(absRoot, normalize(rawPath));
8212
- if (!absTarget.startsWith(absRoot + sep2) && absTarget !== 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 resolve5, sep as sep3 } from "path";
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 = resolve5(getCouncilDir());
8287
- const resolved = resolve5(target);
8288
- if (resolved !== council && !resolved.startsWith(council + sep3)) {
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 resolve6, sep as sep4 } from "path";
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 = resolve6(root);
8487
- const t = resolve6(target);
8488
- if (t !== r && !t.startsWith(r + sep4)) return null;
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 resolve7 } from "path";
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 resolve7(__dirname7, "dashboard");
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 url = `http://localhost:${port}`;
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 resolve8, join as join37 } from "path";
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
- resolve8(__dirname6, "..", "tools", "reinfluence"),
10128
- resolve8(__dirname6, "..", "..", "tools", "reinfluence"),
10161
+ resolve9(__dirname6, "..", "tools", "reinfluence"),
10162
+ resolve9(__dirname6, "..", "..", "tools", "reinfluence"),
10129
10163
  // Dev tree: src/lib/marketing -> ../../../tools/reinfluence
10130
- resolve8(__dirname6, "..", "..", "..", "tools", "reinfluence")
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((resolve9) => q.waiting.push(resolve9));
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((resolve9, reject) => {
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", () => resolve9(data));
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 sep5 = chalk40.dim("\u2501".repeat(25));
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
- ` ${sep5}`,
15803
+ ` ${sep6}`,
15770
15804
  ` ${tagline}`
15771
15805
  ].join("\n");
15772
15806
  return "\n" + logo + text + "\n";
package/install.sh CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  # dreamcontext install script
3
- # Review this script before piping it to sh: https://github.com/meanllbrl/dreamcontext
3
+ # Review this script before piping it to sh: https://www.npmjs.com/package/dreamcontext
4
4
  set -e
5
5
 
6
6
  say() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dreamcontext",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "dreamcontext — the persistent brain for your AI agents. Remembers what you built, knows how your project works.",
5
5
  "type": "module",
6
6
  "bin": {