@work-graph/cli 0.2.6 → 0.2.8

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
@@ -8,7 +8,7 @@ Use it when you want:
8
8
 
9
9
  - a project-local backlog without SaaS lock-in;
10
10
  - BVC work contracts that can be reviewed in Git;
11
- - Cursor MCP configuration generated for the project;
11
+ - optional MCP client configuration for the project (Cursor by default);
12
12
  - a local UI for task status, evidence and project navigation;
13
13
  - repeatable setup through `npx`, not a copied engine repository.
14
14
 
@@ -45,8 +45,8 @@ Typical output:
45
45
  | `.work-graph/config.json` | Project id, label and UI settings |
46
46
  | `intent/` | BVC intent tree for work items |
47
47
  | `intent/index.bvc` | Index of work item files |
48
- | `.cursor/mcp.json` | Cursor MCP server entry for Work Graph |
49
- | `.cursor/rules/work-graph-project.mdc` | Project rule that tells agents to use Work Graph |
48
+ | `.cursor/mcp.json` | Optional: MCP server entry when using Cursor |
49
+ | `.cursor/rules/work-graph-project.mdc` | Optional: project rule for agents in Cursor |
50
50
  | `package.json` | `workgraph:*` scripts and devDependencies |
51
51
 
52
52
  After `npm install`, the project owns its Work Graph runtime through `node_modules/@work-graph/cli` and `node_modules/@work-graph/mcp`.
@@ -55,7 +55,7 @@ After `npm install`, the project owns its Work Graph runtime through `node_modul
55
55
 
56
56
  | Command | Description |
57
57
  |---------|-------------|
58
- | `init [path]` | Scaffold Work Graph into a project: BVC intent tree, config, npm scripts, MCP config and Cursor rule |
58
+ | `init [path]` | Scaffold Work Graph into a project: BVC intent tree, config, npm scripts, optional MCP/rule files |
59
59
  | `ui [path]` | Start the local backlog UI for the project |
60
60
  | `doctor [path]` | Verify that project config, package dependencies and runtime resolution are healthy |
61
61
  | `register [path]` | Optional: register a project in the shared multiproject host |
@@ -116,7 +116,7 @@ The CLI installs the runtime and UI. The BVC format itself is published separate
116
116
  | Symptom | Fix |
117
117
  |---|---|
118
118
  | `work-graph doctor` says dependencies are missing | Run `npm install` in the target project |
119
- | Cursor does not see Work Graph MCP tools | Re-run `npx @work-graph/cli init .` without `--no-mcp`, then reload Cursor MCP servers |
119
+ | Agent does not see Work Graph MCP tools | Re-run `npx @work-graph/cli init .` without `--no-mcp`, then reload MCP servers in your IDE |
120
120
  | UI port is already in use | Run `npx @work-graph/cli ui . --port 4178` |
121
121
  | Existing package scripts were not updated | Re-run without `--no-package` or add the `workgraph:*` scripts manually |
122
122
  | You are hacking Work Graph itself | Use `WORKGRAPH_ENGINE_ROOT=.` or `--engine` from the monorepo, not in normal projects |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@work-graph/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Work Graph CLI — install local agent work tracking, BVC work items and backlog UI in any project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,9 +2,11 @@
2
2
 
3
3
  [MCP](https://modelcontextprotocol.io) server for Work Graph — list, create, and update work items in `intent/**/*.work.bvc`.
4
4
 
5
- ## Cursor
5
+ Works with any MCP-capable agent client (Cursor, Claude Desktop, Claude Code, and others).
6
6
 
7
- After `npx @work-graph/cli init .`, `.cursor/mcp.json` includes:
7
+ ## After `work-graph init`
8
+
9
+ `npx @work-graph/cli init .` writes `.cursor/mcp.json` when you use Cursor. The entry looks like:
8
10
 
9
11
  ```json
10
12
  {
@@ -21,7 +23,7 @@ After `npx @work-graph/cli init .`, `.cursor/mcp.json` includes:
21
23
  }
22
24
  ```
23
25
 
24
- Reload MCP in Cursor after init.
26
+ Reload MCP in your IDE after init. For Claude Desktop / Claude Code and other clients, use the same command and env — see [workgraph-mcp-clients.md](https://github.com/bvc-lang/work-graph/blob/main/docs/workgraph-mcp-clients.md) in the monorepo.
25
27
 
26
28
  ## Standalone
27
29
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@work-graph/mcp",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "MCP server for Work Graph — work items in intent/**/*.work.bvc",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "access": "public"
17
17
  },
18
18
  "dependencies": {
19
- "@work-graph/cli": "^0.2.4",
19
+ "@work-graph/cli": "^0.2.8",
20
20
  "@modelcontextprotocol/sdk": "^1.29.0",
21
21
  "zod": "^3.25.76"
22
22
  },
@@ -340,10 +340,10 @@ server.tool(
340
340
 
341
341
  server.tool(
342
342
  'record_work_item_analysis',
343
- 'Write pre-execution feasibility analysis into WorkItem «Анализ» (стоит ли делать; not post-factum review). Text must come from Cursor LLM — server does not call models.',
343
+ 'Write pre-execution feasibility analysis into WorkItem «Анализ» (стоит ли делать; not post-factum review). Text must come from the connected agent LLM — server does not call models.',
344
344
  {
345
345
  workId: z.string().describe('WorkItem id'),
346
- analysis: z.string().describe('Full analysis text produced in Cursor'),
346
+ analysis: z.string().describe('Full analysis text produced by the connected agent'),
347
347
  },
348
348
  async (args) => jsonText(await recordWorkItemAnalysisFromMcp(args, rootOptions())),
349
349
  );
@@ -8,7 +8,7 @@ const TOOL_RULES = [
8
8
  'New WorkItem prose (Базис/Вектор/Цель/Проверки/title): Russian, min lengths per work-item-bvc-quality; forbidden jargon: closing analysis, Canon:, evidence, upstream, Track A, feeds_epics, depends_on=, «Стоит завести «…» в бэклог».',
9
9
  'Analysis is pre-execution only: feasibility (стоит ли делать), scope, deps, risks, alternatives — NOT a post-factum report of what was built, tests run, or evidence already in the atom.',
10
10
  'Write analysis in present/decision tense («Стоит брать», «Не стоит», «Можно стартовать»), never past retrospective («было оправдано», «можно было») even if work.status is done.',
11
- 'Analysis and decision are written only from Cursor: you (connected LLM) read the task, reason, then call record_work_item_analysis / record_work_item_decision. WorkGraph server never calls LLM.',
11
+ 'Analysis and decision are written only from the connected agent (MCP client LLM): you read the task, reason, then call record_work_item_analysis / record_work_item_decision. WorkGraph server never calls LLM.',
12
12
  'Do not mark a WorkItem done without concrete evidence.',
13
13
  'Keep dashboard/kanban work in WorkGraph UI; MCP is the agent client bridge.',
14
14
  ].join('\n');
@@ -26,7 +26,7 @@ export const workgraphPrompts = {
26
26
  analyze_work_item: {
27
27
  description: 'Pre-execution feasibility analysis for a WorkItem (not post-factum review).',
28
28
  argsSchema: { workId: 'WorkItem id to analyze' },
29
- text: ({ workId }) => `Analyze WorkGraph item ${workId || '<workId>'} in Cursor — **before execution**.
29
+ text: ({ workId }) => `Analyze WorkGraph item ${workId || '<workId>'} — **before execution** (pre-execution feasibility, not a post-mortem).
30
30
 
31
31
  This is a feasibility review (стоит ли делать), NOT a summary of work already done. Do NOT list implemented files, passing tests, or evidence from «Свидетельства» unless you judge future verification risk.
32
32
 
@@ -88,6 +88,43 @@ function renderNav(locale, theme) {
88
88
  </nav>`;
89
89
  }
90
90
 
91
+ const SCREENSHOTS = [
92
+ {
93
+ src: '/assets/img/work-graph-kanban-board-light.png',
94
+ title: { en: 'Kanban board', ru: 'Доска задач' },
95
+ body: { en: 'Local backlog columns with BVC work items and agent ownership.', ru: 'Локальная доска с BVC-задачами и владельцами.' },
96
+ },
97
+ {
98
+ src: '/assets/img/work-graph-analytics-list.png',
99
+ title: { en: 'Analytics list', ru: 'Аналитика' },
100
+ body: { en: 'Decision and research records connected to implementation work.', ru: 'Решения и исследования, связанные с задачами реализации.' },
101
+ },
102
+ {
103
+ src: '/assets/img/work-graph-task-drawer.png',
104
+ title: { en: 'Task contract drawer', ru: 'Контракт задачи' },
105
+ body: { en: 'Basis, Vector, Goal, analysis, decisions and evidence in one drawer.', ru: 'Базис, Вектор, Цель, анализ, решения и evidence в одной панели.' },
106
+ },
107
+ {
108
+ src: '/assets/img/work-graph-verification-matrix.png',
109
+ title: { en: 'Verification matrix', ru: 'Матрица проверок' },
110
+ body: { en: 'Deterministic, optional and environment-dependent gates before done.', ru: 'Детерминированные, опциональные и environment-гейты перед done.' },
111
+ },
112
+ {
113
+ src: '/assets/img/work-graph-architecture-drawer.png',
114
+ title: { en: 'Architecture drawer', ru: 'Архитектура' },
115
+ body: { en: 'Architecture blocks and derived projections for project navigation.', ru: 'Архитектурные блоки и производные проекции для навигации.' },
116
+ },
117
+ {
118
+ src: '/assets/img/work-graph-kanban-board-dark.png',
119
+ title: { en: 'Dark mode', ru: 'Тёмная тема' },
120
+ body: { en: 'The same local board in dark mode.', ru: 'Та же локальная доска в тёмной теме.' },
121
+ },
122
+ ];
123
+
124
+ function screenshotText(value, locale) {
125
+ return value[locale] ?? value.en;
126
+ }
127
+
91
128
  function renderSection(section, copy) {
92
129
  const items = section.items
93
130
  ? `<ol class="flow-list">${section.items.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ol>`
@@ -111,23 +148,29 @@ function renderSection(section, copy) {
111
148
  }
112
149
 
113
150
  function renderHeroVisual(locale) {
114
- const labels = locale === 'ru'
115
- ? ['Аналитика', 'Готово', 'В работе', 'Проверка']
116
- : ['Analytics', 'Ready', 'Doing', 'Verify'];
117
- return `<figure class="template-visual" aria-label="Work Graph board preview">
118
- <div class="visual-toolbar">
119
- <span></span><span></span><span></span>
120
- <strong>Work Graph</strong>
151
+ return `<figure class="template-visual screenshot-hero" aria-label="Work Graph board preview">
152
+ <img src="/assets/img/work-graph-kanban-board-light.png" alt="${escapeAttr(locale === 'ru' ? 'Доска Work Graph' : 'Work Graph kanban board')}" loading="eager" decoding="async">
153
+ <figcaption>${escapeHtml(locale === 'ru' ? 'Локальная доска Work Graph: backlog, ready, in progress and done.' : 'Local Work Graph board: backlog, ready, in progress and done.')}</figcaption>
154
+ </figure>`;
155
+ }
156
+
157
+ function renderScreenshotGallery(locale) {
158
+ return `<section class="screenshot-gallery" aria-label="${escapeAttr(locale === 'ru' ? 'Скриншоты Work Graph' : 'Work Graph screenshots')}">
159
+ <div class="wide-heading">
160
+ <p class="eyebrow">${escapeHtml(locale === 'ru' ? 'Интерфейс' : 'Interface')}</p>
161
+ <h2>${escapeHtml(locale === 'ru' ? 'Как выглядит Work Graph' : 'What Work Graph looks like')}</h2>
162
+ <p>${escapeHtml(locale === 'ru' ? 'Реальные экраны локального UI: доска, аналитика, контракты задач, проверки и архитектура.' : 'Real local UI screens: board, analytics, task contracts, verification and architecture.')}</p>
121
163
  </div>
122
- <div class="visual-board">
123
- ${labels.map((label, columnIndex) => `<div class="visual-column">
124
- <h3>${escapeHtml(label)}</h3>
125
- ${[0, 1, 2].map((cardIndex) => `<div class="visual-card is-${(columnIndex + cardIndex) % 4}">
126
- <span></span><p></p><small></small>
127
- </div>`).join('')}
128
- </div>`).join('')}
164
+ <div class="screenshot-grid">
165
+ ${SCREENSHOTS.map((shot) => `<figure class="screenshot-card">
166
+ <img src="${escapeAttr(shot.src)}" alt="${escapeAttr(screenshotText(shot.title, locale))}" loading="lazy" decoding="async">
167
+ <figcaption>
168
+ <strong>${escapeHtml(screenshotText(shot.title, locale))}</strong>
169
+ <span>${escapeHtml(screenshotText(shot.body, locale))}</span>
170
+ </figcaption>
171
+ </figure>`).join('')}
129
172
  </div>
130
- </figure>`;
173
+ </section>`;
131
174
  }
132
175
 
133
176
  function renderTemplateAside(copy, locale, theme) {
@@ -176,12 +219,12 @@ function renderRelatedTemplates(locale, theme) {
176
219
  ? [
177
220
  ['Контракт задачи', 'BVC-атом с Базисом, Вектором, Целью и проверками.', '/docs/bvc-spec'],
178
221
  ['Матрица проверок', 'Tier A/B/C readiness для работы агентов.', '/docs/verification-matrix'],
179
- ['MCP-инструменты', 'Контракты tools для Cursor и Claude Code.', '/docs/mcp-tools'],
222
+ ['MCP-инструменты', 'Контракты tools для MCP-клиентов (Cursor, Claude Code, …).', '/docs/mcp-tools'],
180
223
  ]
181
224
  : [
182
225
  ['Work contract', 'BVC atom with Basis, Vector, Goal and checks.', '/docs/bvc-spec'],
183
226
  ['Verification matrix', 'Tier A/B/C readiness for agent work.', '/docs/verification-matrix'],
184
- ['MCP tools', 'Tool contracts for Cursor and Claude Code.', '/docs/mcp-tools'],
227
+ ['MCP tools', 'Tool contracts for MCP clients (Cursor, Claude Code, …).', '/docs/mcp-tools'],
185
228
  ];
186
229
  return `<section class="related-templates">
187
230
  <div class="related-inner">
@@ -347,7 +390,7 @@ function renderInstallInstructions(locale) {
347
390
  <div>
348
391
  <p class="eyebrow">${escapeHtml(locale === 'ru' ? 'Установка' : 'Installation')}</p>
349
392
  <h2>${escapeHtml(locale === 'ru' ? 'Как установить Work Graph' : 'How to install Work Graph')}</h2>
350
- <p>${escapeHtml(locale === 'ru' ? 'Быстрый путь — подключить Work Graph как MCP-сервер к Cursor или Claude Code. Данные остаются локально в git, сайт собирается в dist/public-site без базы данных.' : 'The fastest path is to connect Work Graph as an MCP server to Cursor or Claude Code. Data stays local in git, and the site exports to dist/public-site without a database.')}</p>
393
+ <p>${escapeHtml(locale === 'ru' ? 'Быстрый путь — подключить Work Graph как MCP-сервер к вашему агенту (Cursor, Claude Code или другой MCP-клиент). Данные остаются локально в git.' : 'The fastest path is to connect Work Graph as an MCP server to your agent IDE (Cursor, Claude Code, or another MCP client). Data stays local in git.')}</p>
351
394
  </div>
352
395
  <ol>${steps.map(([title, command]) => `<li>
353
396
  <strong>${escapeHtml(title)}</strong>
@@ -549,6 +592,8 @@ export function renderPublicSiteHtml(page, options = {}) {
549
592
  .hero p { color: var(--muted); font-size: 1.125rem; line-height: 1.7; max-width: 820px; }
550
593
  .cta-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
551
594
  .template-visual { background: var(--card); border: 1px solid var(--border); border-radius: 4px; box-shadow: var(--shadow-raised); margin: 0 auto 56px; max-width: 1040px; overflow: hidden; }
595
+ .screenshot-hero img { display: block; height: auto; width: 100%; }
596
+ .screenshot-hero figcaption { border-top: 1px solid var(--border); color: var(--muted); font-size: 13px; padding: 12px 16px; }
552
597
  .visual-toolbar { align-items: center; background: #fafbfc; border-bottom: 1px solid var(--border); display: flex; gap: 8px; padding: 12px 14px; }
553
598
  .visual-toolbar span { background: var(--border); border-radius: 999px; height: 8px; width: 8px; }
554
599
  .visual-board { background: #f4f5f7; display: grid; gap: 12px; grid-template-columns: repeat(4, 1fr); min-height: 310px; padding: 18px; }
@@ -587,6 +632,12 @@ export function renderPublicSiteHtml(page, options = {}) {
587
632
  .proof-stats strong { color: var(--accent); display: block; font-size: clamp(1.8rem, 4vw, 3rem); letter-spacing: -.04em; line-height: 1; }
588
633
  .proof-stats span { color: var(--muted); display: block; font-size: 13px; margin-top: 8px; }
589
634
  .graph-trinity, .pillar-section, .install-section, .code-showcase, .audience-section, .comparison-strip, .roadmap-faq { margin: 58px auto 0; max-width: 1200px; }
635
+ .screenshot-gallery { margin: 58px auto 0; max-width: 1200px; }
636
+ .screenshot-grid { display: grid; gap: 18px; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 18px; }
637
+ .screenshot-card { background: var(--card); border: 1px solid var(--border); border-radius: 4px; box-shadow: var(--shadow); margin: 0; overflow: hidden; }
638
+ .screenshot-card img { display: block; height: auto; width: 100%; }
639
+ .screenshot-card figcaption { border-top: 1px solid var(--border); display: grid; gap: 4px; padding: 14px; }
640
+ .screenshot-card figcaption span { color: var(--muted); font-size: 13px; }
590
641
  .wide-heading { max-width: 900px; }
591
642
  .graph-flow { display: grid; gap: 18px; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 20px; }
592
643
  .graph-flow article { background: var(--card); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); padding: 22px; position: relative; }
@@ -655,7 +706,7 @@ export function renderPublicSiteHtml(page, options = {}) {
655
706
  .visual-board { grid-template-columns: repeat(2, 1fr); }
656
707
  .content-layout { grid-template-columns: 1fr; }
657
708
  .template-aside { position: static; }
658
- .proof-stats, .graph-flow, .pillar-grid, .install-section, .code-showcase, .audience-section > div, .comparison-strip > div, .roadmap-faq, .related-grid, .footer-columns { grid-template-columns: 1fr; }
709
+ .proof-stats, .graph-flow, .pillar-grid, .install-section, .code-showcase, .audience-section > div, .comparison-strip > div, .roadmap-faq, .related-grid, .footer-columns, .screenshot-grid { grid-template-columns: 1fr; }
659
710
  .graph-flow article + article::before { content: none; }
660
711
  table { display: block; overflow-x: auto; white-space: nowrap; }
661
712
  }
@@ -686,7 +737,7 @@ export function renderPublicSiteHtml(page, options = {}) {
686
737
  ${renderSteps(copy, locale)}
687
738
  </div>
688
739
  </div>
689
- ${page.kind === 'home' ? renderHomeExpansion(locale) : ''}`}
740
+ ${page.kind === 'home' ? `${renderScreenshotGallery(locale)}${renderHomeExpansion(locale)}` : ''}`}
690
741
  </article>
691
742
  ${renderRelatedTemplates(locale, theme)}
692
743
  ${renderBottomCta(copy, locale, theme)}
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { join } from 'node:path';
5
+ import { pathToFileURL } from 'node:url';
6
+
7
+ import { handlePublicSiteRequest } from './publicSiteServer.mjs';
8
+ import { resolveInstallLayout } from './workGraphInstallLayout.mjs';
9
+
10
+ const DEFAULT_HOST = '127.0.0.1';
11
+ const DEFAULT_PORT = 4178;
12
+
13
+ const { PUBLIC_ROOT, DESIGN_TOKENS_WG_CSS_PATH } = resolveInstallLayout({ moduleUrl: import.meta.url });
14
+
15
+ function sendText(response, statusCode, body, contentType = 'text/plain') {
16
+ response.writeHead(statusCode, {
17
+ 'content-type': `${contentType}; charset=utf-8`,
18
+ 'cache-control': 'no-store',
19
+ });
20
+ response.end(body);
21
+ }
22
+
23
+ function serveFile(response, filePath, contentType) {
24
+ try {
25
+ const source = readFileSync(filePath);
26
+ response.writeHead(200, {
27
+ 'content-type': contentType,
28
+ 'cache-control': 'public, max-age=3600',
29
+ });
30
+ response.end(source);
31
+ } catch {
32
+ sendText(response, 404, 'not_found');
33
+ }
34
+ }
35
+
36
+ function serveAssetDir(url, response, rootDir, urlPrefix) {
37
+ if (!url.pathname.startsWith(urlPrefix)) return false;
38
+ const relativePath = decodeURIComponent(url.pathname.slice(urlPrefix.length));
39
+ if (!relativePath || relativePath.includes('..')) {
40
+ sendText(response, 403, 'forbidden');
41
+ return true;
42
+ }
43
+ const filePath = join(rootDir, relativePath);
44
+ if (!filePath.startsWith(rootDir)) {
45
+ sendText(response, 403, 'forbidden');
46
+ return true;
47
+ }
48
+ const contentType = filePath.endsWith('.svg')
49
+ ? 'image/svg+xml; charset=utf-8'
50
+ : filePath.endsWith('.png')
51
+ ? 'image/png'
52
+ : filePath.endsWith('.css')
53
+ ? 'text/css; charset=utf-8'
54
+ : filePath.endsWith('.woff2')
55
+ ? 'font/woff2'
56
+ : 'application/octet-stream';
57
+ serveFile(response, filePath, contentType);
58
+ return true;
59
+ }
60
+
61
+ export function createPublicSiteServer() {
62
+ return createServer((request, response) => {
63
+ const url = new URL(request.url ?? '/', `http://${DEFAULT_HOST}`);
64
+ const method = request.method ?? 'GET';
65
+ if (method !== 'GET') {
66
+ sendText(response, 405, 'method_not_allowed');
67
+ return;
68
+ }
69
+
70
+ if (serveAssetDir(url, response, join(PUBLIC_ROOT, 'assets', 'img'), '/assets/img/')) return;
71
+ if (serveAssetDir(url, response, join(PUBLIC_ROOT, 'assets', 'icons'), '/assets/icons/')) return;
72
+ if (serveAssetDir(url, response, join(PUBLIC_ROOT, 'assets', 'avatars'), '/assets/avatars/')) return;
73
+ if (serveAssetDir(url, response, join(PUBLIC_ROOT, 'fonts'), '/assets/fonts/')) return;
74
+
75
+ if (url.pathname === '/assets/favicon.svg') {
76
+ serveFile(response, join(PUBLIC_ROOT, 'assets', 'favicon.svg'), 'image/svg+xml; charset=utf-8');
77
+ return;
78
+ }
79
+
80
+ if (url.pathname === '/assets/design-tokens-workgraph-dark.css') {
81
+ serveFile(response, DESIGN_TOKENS_WG_CSS_PATH, 'text/css; charset=utf-8');
82
+ return;
83
+ }
84
+
85
+ if (handlePublicSiteRequest(request, response, url)) return;
86
+ sendText(response, 404, 'not_found');
87
+ });
88
+ }
89
+
90
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
91
+ const host = process.env.WORKGRAPH_PUBLIC_SITE_HOST ?? DEFAULT_HOST;
92
+ const port = Number(process.env.WORKGRAPH_PUBLIC_SITE_PORT ?? DEFAULT_PORT);
93
+ const server = createPublicSiteServer();
94
+ server.listen(port, host, () => {
95
+ console.log(`Work Graph public site: http://${host}:${port}/`);
96
+ });
97
+ }
@@ -18,7 +18,6 @@ import {
18
18
  import { buildCodeGapOperatorProjection } from './codeGapOperatorProjection.mjs';
19
19
  import { buildEpicWorkScopeSlice } from './epicWorkScope.mjs';
20
20
  import { renderUiKitPageHtml } from './ui/pages/uiKitPage.mjs';
21
- import { handlePublicSiteRequest } from './publicSiteServer.mjs';
22
21
  import { UI_BUTTON_CSS } from './ui/atoms/button.mjs';
23
22
  import { UI_BADGE_CSS } from './ui/atoms/badge.mjs';
24
23
  import { UI_SELECT_CSS } from './ui/atoms/select.mjs';
@@ -140,6 +139,13 @@ function tryServePublicAvatarsAsset(url, response) {
140
139
  return tryServePublicAssetDir(url, response, join(PUBLIC_ROOT, 'assets', 'avatars'), '/assets/avatars/');
141
140
  }
142
141
 
142
+ function tryServePublicImagesAsset(url, response) {
143
+ if (!url.pathname.startsWith('/assets/img/')) {
144
+ return false;
145
+ }
146
+ return tryServePublicAssetDir(url, response, join(PUBLIC_ROOT, 'assets', 'img'), '/assets/img/');
147
+ }
148
+
143
149
  function tryServePublicAssetDir(url, response, rootDir, urlPrefix) {
144
150
  const relativePath = decodeURIComponent(url.pathname.slice(urlPrefix.length));
145
151
  if (!relativePath || relativePath.includes('..')) {
@@ -155,7 +161,9 @@ function tryServePublicAssetDir(url, response, rootDir, urlPrefix) {
155
161
  const source = readFileSync(filePath);
156
162
  const contentType = filePath.endsWith('.svg')
157
163
  ? 'image/svg+xml; charset=utf-8'
158
- : 'application/octet-stream';
164
+ : filePath.endsWith('.png')
165
+ ? 'image/png'
166
+ : 'application/octet-stream';
159
167
  response.writeHead(200, {
160
168
  'content-type': contentType,
161
169
  'cache-control': 'public, max-age=3600',
@@ -9878,10 +9886,6 @@ export function createBacklogUiServer(options = {}) {
9878
9886
  const cwd = requestCtx.repoRoot;
9879
9887
  const serverOptions = { cwd, backlogPath, journalPath, auditPath };
9880
9888
 
9881
- if (handlePublicSiteRequest(request, response, url)) {
9882
- return;
9883
- }
9884
-
9885
9889
  if (url.pathname === '/api/prompt-rules-projection' && method === 'GET') {
9886
9890
  try {
9887
9891
  const ruleId = url.searchParams.get('ruleId') ?? url.searchParams.get('id') ?? undefined;
@@ -10690,6 +10694,9 @@ export function createBacklogUiServer(options = {}) {
10690
10694
  if (tryServePublicAvatarsAsset(url, response)) {
10691
10695
  return;
10692
10696
  }
10697
+ if (tryServePublicImagesAsset(url, response)) {
10698
+ return;
10699
+ }
10693
10700
 
10694
10701
  if (url.pathname === '/favicon.ico' && method === 'GET') {
10695
10702
  response.writeHead(204);
@@ -10772,7 +10779,7 @@ export function createBacklogUiServer(options = {}) {
10772
10779
  return;
10773
10780
  }
10774
10781
 
10775
- if (url.pathname === '/app' || url.pathname === '/app/' || url.pathname === '/app/index.html') {
10782
+ if (url.pathname === '/' || url.pathname === '/app' || url.pathname === '/app/' || url.pathname === '/app/index.html') {
10776
10783
  const locale = resolveUiLocale({
10777
10784
  cookieHeader: request.headers.cookie,
10778
10785
  acceptLanguage: request.headers['accept-language'],
@@ -20,8 +20,8 @@ export {
20
20
 
21
21
  const CONFIG_SCHEMA_V1 = 'workgraph.project.config.v1';
22
22
  const CONFIG_SCHEMA_V2 = 'workgraph.project.config.v2';
23
- const DEFAULT_CLI_VERSION = '0.2.3';
24
- const DEFAULT_MCP_VERSION = '0.2.3';
23
+ const DEFAULT_CLI_VERSION = '0.2.8';
24
+ const DEFAULT_MCP_VERSION = '0.2.5';
25
25
 
26
26
  const INDEX_STUB = `#Index<[
27
27
  WorkItems:
@@ -322,12 +322,12 @@ export async function initWorkGraphProject(options = {}) {
322
322
  'npm install',
323
323
  'npm run workgraph:ui',
324
324
  'Открыть http://127.0.0.1:4177/',
325
- 'Перезагрузить MCP в Cursor (workgraph)',
325
+ 'Перезагрузить MCP в IDE (сервер workgraph)',
326
326
  ]
327
327
  : [
328
328
  'npm run workgraph:ui',
329
329
  'Открыть http://127.0.0.1:4177/',
330
- 'Перезагрузить MCP в Cursor (workgraph)',
330
+ 'Перезагрузить MCP в IDE (сервер workgraph)',
331
331
  ];
332
332
 
333
333
  return {