mdboard 1.3.0 → 2.1.0

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.
Files changed (53) hide show
  1. package/bin.js +117 -59
  2. package/index.html +2161 -1579
  3. package/package.json +7 -5
  4. package/presets/kanban/api.json +91 -0
  5. package/presets/kanban/cli.json +69 -0
  6. package/presets/kanban/docs.json +29 -0
  7. package/presets/kanban/entities.json +128 -0
  8. package/presets/kanban/structure.json +15 -0
  9. package/presets/kanban/ui.json +86 -0
  10. package/presets/scrum/api.json +98 -0
  11. package/presets/scrum/cli.json +120 -0
  12. package/presets/scrum/docs.json +43 -0
  13. package/presets/scrum/entities.json +268 -0
  14. package/presets/scrum/structure.json +32 -0
  15. package/presets/scrum/ui.json +201 -0
  16. package/presets/shape-up/api.json +40 -0
  17. package/presets/shape-up/cli.json +44 -0
  18. package/presets/shape-up/docs.json +32 -0
  19. package/presets/shape-up/entities.json +140 -0
  20. package/presets/shape-up/structure.json +28 -0
  21. package/presets/shape-up/ui.json +114 -0
  22. package/src/cli/cli.js +186 -210
  23. package/src/cli/config.js +234 -0
  24. package/src/cli/init.js +128 -76
  25. package/src/cli/preset.js +849 -0
  26. package/src/cli/skill.js +417 -0
  27. package/src/cli/status.js +126 -96
  28. package/src/core/config.js +491 -38
  29. package/src/core/history.js +17 -1
  30. package/src/core/scanner.js +373 -463
  31. package/src/core/workspace.js +0 -15
  32. package/src/server/api.js +464 -741
  33. package/src/server/server.js +105 -130
  34. package/build.js +0 -44
  35. package/defaults.json +0 -43
  36. package/src/cli/sync.js +0 -194
  37. package/src/cli/theme.js +0 -142
  38. package/src/client/app.js +0 -266
  39. package/src/client/board.js +0 -157
  40. package/src/client/core.js +0 -331
  41. package/src/client/editor.js +0 -318
  42. package/src/client/history.js +0 -137
  43. package/src/client/metrics.js +0 -38
  44. package/src/client/milestones.js +0 -77
  45. package/src/client/notes.js +0 -183
  46. package/src/client/overview.js +0 -104
  47. package/src/client/panel.js +0 -637
  48. package/src/client/styles.css +0 -471
  49. package/src/client/table.js +0 -111
  50. package/src/client/template.html +0 -144
  51. package/src/client/themes.js +0 -261
  52. package/src/client/workspace.js +0 -164
  53. package/src/core/agent-scanner.js +0 -260
@@ -0,0 +1,234 @@
1
+ /**
2
+ * mdboard config — Interactive configuration wizard
3
+ *
4
+ * Guides the user through initial setup:
5
+ * Step 1: Methodology (preset selection)
6
+ * Step 2: Theme preference
7
+ * Step 3: Configuration overview (informational)
8
+ * Step 4: AI Skill installation
9
+ *
10
+ * Usage:
11
+ * mdboard config
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+ const readline = require('readline');
20
+ const configEngine = require('../core/config');
21
+ const { installSkill, AGENTS } = require('./skill');
22
+
23
+ var cwd = process.cwd();
24
+
25
+ /**
26
+ * Run the config wizard.
27
+ *
28
+ * @param {string[]} args - CLI arguments after 'config'
29
+ */
30
+ function run(args) {
31
+ var rl = readline.createInterface({ input: process.stdin, output: process.stdout });
32
+
33
+ console.log('\n mdboard config — Setup wizard\n');
34
+
35
+ stepMethodology(rl);
36
+ }
37
+
38
+ // --- Step 1: Methodology ---
39
+
40
+ function stepMethodology(rl) {
41
+ // Read available presets
42
+ var presets = [];
43
+ try {
44
+ presets = fs.readdirSync(configEngine.PRESETS_DIR).filter(function (d) {
45
+ return fs.statSync(path.join(configEngine.PRESETS_DIR, d)).isDirectory();
46
+ });
47
+ } catch (e) { /* ignore */ }
48
+
49
+ console.log(' Step 1: Methodology\n');
50
+ for (var i = 0; i < presets.length; i++) {
51
+ console.log(' ' + (i + 1) + ') ' + presets[i]);
52
+ }
53
+ console.log('');
54
+
55
+ var currentPreset = configEngine.DEFAULT_PRESET;
56
+ try {
57
+ var cfg = configEngine.loadConfig(cwd, process.env.MDBOARD_CONFIG);
58
+ currentPreset = cfg._preset;
59
+ } catch (e) { /* ignore */ }
60
+
61
+ rl.question(' Choice [' + currentPreset + ']: ', function (answer) {
62
+ var choice = answer.trim();
63
+ var preset = currentPreset;
64
+
65
+ if (choice !== '') {
66
+ var idx = parseInt(choice, 10);
67
+ if (idx >= 1 && idx <= presets.length) {
68
+ preset = presets[idx - 1];
69
+ } else if (presets.includes(choice)) {
70
+ preset = choice;
71
+ } else {
72
+ console.log(' Preset "' + choice + '" not yet available. Using ' + currentPreset + '.');
73
+ }
74
+ }
75
+
76
+ // Set for the rest of the process
77
+ process.env.MDBOARD_PRESET = preset;
78
+
79
+ // Scaffold if project/ doesn't exist
80
+ var projectPath = path.join(cwd, 'project');
81
+ if (!fs.existsSync(projectPath)) {
82
+ console.log(' No project/ found — scaffolding with ' + preset + ' preset...');
83
+ try {
84
+ require('./init');
85
+ } catch (e) {
86
+ // init.js may call process.exit, so we catch and continue
87
+ }
88
+ } else {
89
+ console.log(' Methodology: ' + preset);
90
+ console.log(' project/ already exists — keeping existing data.\n');
91
+ }
92
+
93
+ stepTheme(rl, preset);
94
+ });
95
+ }
96
+
97
+ // --- Step 2: Theme ---
98
+
99
+ function stepTheme(rl, preset) {
100
+ console.log(' Step 2: Theme\n');
101
+ console.log(' 1) default Dark theme (built-in)');
102
+ console.log(' 2) light Light theme');
103
+ console.log(' 3) custom Edit project/mdboard.css\n');
104
+
105
+ rl.question(' Choice [default]: ', function (answer) {
106
+ var choice = answer.trim();
107
+ var theme = 'default';
108
+
109
+ if (choice === '2' || choice === 'light') {
110
+ theme = 'light';
111
+ } else if (choice === '3' || choice === 'custom') {
112
+ theme = 'custom';
113
+ }
114
+
115
+ // Save theme preference
116
+ if (theme !== 'default') {
117
+ var globalDir = path.join(os.homedir(), '.config', 'mdboard');
118
+ if (!fs.existsSync(globalDir)) {
119
+ fs.mkdirSync(globalDir, { recursive: true });
120
+ }
121
+ var configPath = path.join(globalDir, 'mdboard.json');
122
+ var globalCfg = {};
123
+ try { globalCfg = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } catch (e) { /* */ }
124
+ globalCfg.theme = theme;
125
+ fs.writeFileSync(configPath, JSON.stringify(globalCfg, null, 2) + '\n', 'utf-8');
126
+ console.log(' Theme saved: ' + theme + '\n');
127
+ } else {
128
+ console.log(' Theme: default\n');
129
+ }
130
+
131
+ stepConfiguration(rl, preset);
132
+ });
133
+ }
134
+
135
+ // --- Step 3: Configuration overview ---
136
+
137
+ function stepConfiguration(rl, preset) {
138
+ console.log(' Step 3: Configuration\n');
139
+
140
+ var cfg;
141
+ try {
142
+ cfg = configEngine.loadConfig(cwd, process.env.MDBOARD_CONFIG);
143
+ } catch (e) {
144
+ cfg = configEngine.loadConfig(null);
145
+ }
146
+
147
+ var types = configEngine.getEntityTypes(cfg);
148
+ var flat = configEngine.flattenHierarchy(cfg);
149
+ var hierarchy = flat.filter(function (e) { return !e.standalone; });
150
+ var chain = hierarchy.map(function (e) {
151
+ var entity = configEngine.getEntity(cfg, e.type);
152
+ return entity ? entity.singular : e.type;
153
+ }).join(' > ');
154
+
155
+ console.log(' Preset: ' + preset);
156
+ console.log(' Hierarchy: ' + chain);
157
+ console.log(' Entities: ' + types.join(', '));
158
+ console.log('');
159
+ console.log(' Config files (in presets/' + preset + '/):');
160
+ console.log(' entities.json Entity definitions, fields, statuses');
161
+ console.log(' structure.json Directory layout & hierarchy');
162
+ console.log(' cli.json CLI command config');
163
+ console.log(' api.json API endpoint config');
164
+ console.log(' ui.json Dashboard UI tabs & layout');
165
+ console.log(' docs.json Documentation & status report');
166
+ console.log('');
167
+ console.log(' Override any file by placing it in project/<file>.json');
168
+ console.log('');
169
+
170
+ stepSkill(rl);
171
+ }
172
+
173
+ // --- Step 4: AI Skill ---
174
+
175
+ function stepSkill(rl) {
176
+ console.log(' Step 4: AI Skill\n');
177
+ console.log(' Install an AI skill so your IDE/agent understands this project.\n');
178
+ console.log(' 1) Claude Code ~/.claude/skills/mdboard/SKILL.md');
179
+ console.log(' 2) Cursor .cursor/rules/mdboard.mdc');
180
+ console.log(' 3) Windsurf .windsurf/rules/mdboard.md');
181
+ console.log(' 4) VS Code .claude/skills/mdboard/SKILL.md');
182
+ console.log(' 5) Custom path');
183
+ console.log(' 6) Skip\n');
184
+
185
+ rl.question(' Choice [6]: ', function (answer) {
186
+ var choice = answer.trim() || '6';
187
+
188
+ if (choice === '6') {
189
+ finishWizard(rl);
190
+ return;
191
+ }
192
+
193
+ if (choice === '5') {
194
+ rl.question(' Path: ', function (pathAnswer) {
195
+ var p = path.resolve(pathAnswer.trim());
196
+ var fmt = p.endsWith('.mdc') ? 'mdc' : 'skill-md';
197
+ installSkill(cwd, p, fmt);
198
+ console.log(' Skill installed: ' + p);
199
+ finishWizard(rl);
200
+ });
201
+ return;
202
+ }
203
+
204
+ var agentKeys = ['claude-code', 'cursor', 'windsurf', 'vscode'];
205
+ var idx = parseInt(choice, 10) - 1;
206
+ if (idx < 0 || idx >= agentKeys.length) {
207
+ console.log(' Invalid choice — skipping.');
208
+ finishWizard(rl);
209
+ return;
210
+ }
211
+
212
+ var agent = AGENTS[agentKeys[idx]];
213
+ var fp = agent.path();
214
+ installSkill(cwd, fp, agent.format);
215
+ console.log(' Skill installed: ' + agent.label + ' → ' + fp);
216
+ finishWizard(rl);
217
+ });
218
+ }
219
+
220
+ // --- Done ---
221
+
222
+ function finishWizard(rl) {
223
+ console.log('\n Setup complete!\n');
224
+ console.log(' Next steps:');
225
+ console.log(' mdboard Start the dashboard');
226
+ console.log(' mdboard create ... Create your first entity');
227
+ console.log(' mdboard preset list Switch methodology or create a custom preset');
228
+ console.log(' mdboard skill Re-install skill anytime');
229
+ console.log('');
230
+ rl.close();
231
+ process.exit(0);
232
+ }
233
+
234
+ module.exports = { run };
package/src/cli/init.js CHANGED
@@ -1,22 +1,53 @@
1
- #!/usr/bin/env node
1
+ /**
2
+ * mdboard init — Scaffold a new workspace with preset support
3
+ *
4
+ * Usage:
5
+ * mdboard init [name] [--preset shape-up]
6
+ *
7
+ * Creates the project/ directory structure based on the selected methodology preset.
8
+ * All directories and files are generated from the preset's structure.json config.
9
+ */
10
+
11
+ 'use strict';
2
12
 
3
13
  const fs = require('fs');
4
14
  const path = require('path');
5
15
  const readline = require('readline');
6
- const { loadConfig } = require('../core/config');
16
+ const configEngine = require('../core/config');
7
17
 
8
18
  const cwd = process.cwd();
9
19
  const projectPath = path.join(cwd, 'project');
10
20
  const today = new Date().toISOString().split('T')[0];
11
- const config = loadConfig(cwd);
21
+ const args = process.argv.slice(2).filter(a => a !== 'init');
12
22
 
23
+ // Parse flags
24
+ const preset = process.env.MDBOARD_PRESET || parseFlag('preset') || configEngine.DEFAULT_PRESET;
25
+ const nameArg = args.filter(a => !a.startsWith('--'))[0] || '';
26
+
27
+ function parseFlag(flag) {
28
+ for (let i = 0; i < args.length; i++) {
29
+ if (args[i] === '--' + flag && args[i + 1]) return args[i + 1];
30
+ }
31
+ return null;
32
+ }
33
+
34
+ // Check if project already exists
13
35
  if (fs.existsSync(projectPath)) {
14
- console.log(`\n project/ already exists at ${projectPath}`);
36
+ console.log('\n project/ already exists at ' + projectPath);
15
37
  console.log(' Skipping init to avoid overwriting existing data.\n');
16
38
  process.exit(0);
17
39
  }
18
40
 
19
- const nameArg = (process.env.MDBOARD_INIT_NAME || '').trim();
41
+ // Validate preset exists
42
+ const presetDir = path.join(configEngine.PRESETS_DIR, preset);
43
+ if (!fs.existsSync(presetDir)) {
44
+ const available = fs.readdirSync(configEngine.PRESETS_DIR).filter(d => {
45
+ return fs.statSync(path.join(configEngine.PRESETS_DIR, d)).isDirectory();
46
+ });
47
+ console.error('\n Error: unknown preset "' + preset + '"');
48
+ console.error(' Available presets: ' + available.join(', ') + '\n');
49
+ process.exit(1);
50
+ }
20
51
 
21
52
  if (nameArg) {
22
53
  scaffold(nameArg);
@@ -30,94 +61,115 @@ if (nameArg) {
30
61
  }
31
62
 
32
63
  function scaffold(name) {
33
- const msDir = config.entities.milestone.dir;
34
-
35
- // Create directories
64
+ // Load preset config
65
+ const cfg = configEngine.loadConfig(null);
66
+ const structure = cfg.structure || {};
67
+ const root = structure.root || 'project';
68
+ const hierarchy = structure.hierarchy || {};
69
+ const standalone = structure.standalone || {};
70
+
71
+ // Create root directory
36
72
  fs.mkdirSync(projectPath, { recursive: true });
37
- fs.mkdirSync(path.join(projectPath, msDir), { recursive: true });
38
- // PROJECT.md
39
- fs.writeFileSync(path.join(projectPath, 'PROJECT.md'), `---
40
- name: "${name}"
41
- description: ""
42
- created: ${today}
43
- ---
44
-
45
- # ${name}
46
-
47
- ## Overview
48
73
 
49
- Describe your project here.
74
+ // Create hierarchy directories
75
+ createHierarchyDirs(projectPath, hierarchy);
50
76
 
51
- ## Objectives
52
-
53
- -
77
+ // Create standalone directories
78
+ for (const [type, def] of Object.entries(standalone)) {
79
+ fs.mkdirSync(path.join(projectPath, def.dir), { recursive: true });
80
+ }
54
81
 
55
- ## Scope
82
+ // Create archive directory if defined
83
+ if (structure.archive && structure.archive.dir) {
84
+ fs.mkdirSync(path.join(projectPath, structure.archive.dir), { recursive: true });
85
+ }
56
86
 
57
- -
87
+ // PROJECT.md
88
+ fs.writeFileSync(path.join(projectPath, 'PROJECT.md'), '---\nname: "' + name + '"\ndescription: ""\ncreated: ' + today + '\nmethodology: ' + preset + '\n---\n\n# ' + name + '\n\n## Overview\n\nDescribe your project here.\n\n## Objectives\n\n-\n\n## Scope\n\n-\n', 'utf-8');
58
89
 
59
- `, 'utf-8');
60
-
61
- // mdboard.json — example config
62
- fs.writeFileSync(path.join(projectPath, 'mdboard.json'), `{
63
- "entities": {
64
- "task": {
65
- "singular": "Task",
66
- "plural": "Tasks",
67
- "prefix": "TASK"
68
- }
69
- }
70
- }
71
- `, 'utf-8');
90
+ // mdboard.json — project config (preset + theme)
91
+ fs.writeFileSync(path.join(projectPath, 'mdboard.json'), JSON.stringify({ preset: preset }, null, 2) + '\n', 'utf-8');
72
92
 
73
93
  // mdboard.css — example custom theme
74
- fs.writeFileSync(path.join(projectPath, 'mdboard.css'), `/* mdboard custom theme
75
- Override any CSS variable from the dashboard.
76
- Example:
77
- :root {
78
- --bg: #FFFFFF;
79
- --surface: #F8F9FA;
80
- --text: #212529;
81
- --accent: #0969DA;
82
- }
83
- */
84
- `, 'utf-8');
85
-
86
- // workspace.json — default workspace with current project as source
94
+ fs.writeFileSync(path.join(projectPath, 'mdboard.css'), '/* mdboard custom theme\n Override any CSS variable from the dashboard.\n Example:\n :root {\n --bg: #FFFFFF;\n --surface: #F8F9FA;\n --text: #212529;\n --accent: #0969DA;\n }\n*/\n', 'utf-8');
95
+
96
+ // workspace.json
87
97
  const wsPath = path.join(cwd, 'workspace.json');
88
98
  if (!fs.existsSync(wsPath)) {
99
+ const slug = name.toLowerCase().replace(/\s+/g, '-');
89
100
  fs.writeFileSync(wsPath, JSON.stringify({
90
101
  name: name,
91
102
  sources: [
92
- { name: name.toLowerCase().replace(/\s+/g, '-'), label: name, path: ".", color: "#5B6EF5" }
103
+ { name: slug, label: name, path: '.', color: '#5B6EF5' }
93
104
  ],
94
105
  settings: {}
95
106
  }, null, 2) + '\n', 'utf-8');
96
107
  }
97
108
 
98
- console.log(`
99
- mdboard init workspace "${name}" scaffolded
100
-
101
- Created:
102
- project/PROJECT.md
103
- project/${msDir}/
104
- project/mdboard.json
105
- project/mdboard.css
106
- workspace.json
107
-
108
- Next steps:
109
- 1. Edit project/PROJECT.md with your project details
110
- 2. Create milestones and epics under project/${msDir}/
111
- 3. Customize project/mdboard.json for your workflow
112
- 4. Run \`mdboard\` to start the dashboard
113
-
114
- Add sources to workspace.json to manage multiple repos:
115
- {
116
- "name": "${name}",
117
- "sources": [
118
- { "name": "api", "path": "../api-repo", "icon": "🔧" },
119
- { "name": "web", "path": "../web-repo", "icon": "🌐" }
120
- ]
109
+ // Build directory listing for output
110
+ const dirs = collectDirs(hierarchy, standalone, structure.archive);
111
+
112
+ console.log('\n mdboard init — workspace "' + name + '" scaffolded');
113
+ console.log(' Preset: ' + preset + '\n');
114
+ console.log(' Created:');
115
+ console.log(' project/PROJECT.md');
116
+ for (const d of dirs) {
117
+ console.log(' project/' + d + '/');
118
+ }
119
+ console.log(' project/mdboard.css');
120
+ console.log(' workspace.json');
121
+
122
+ console.log('\n Next steps:');
123
+ console.log(' 1. Edit project/PROJECT.md with your project details');
124
+
125
+ // Show first create command example from hierarchy
126
+ const firstType = Object.keys(hierarchy)[0];
127
+ if (firstType) {
128
+ const entity = configEngine.getEntity(cfg, firstType);
129
+ if (entity) {
130
+ console.log(' 2. Create your first ' + entity.singular.toLowerCase() + ':');
131
+ console.log(' mdboard create ' + firstType + ' "My ' + entity.singular + '"');
132
+ }
133
+ }
134
+
135
+ console.log(' 3. Run `mdboard` to start the dashboard');
136
+ console.log('\n Other presets available:');
137
+ console.log(' mdboard preset list See all methodologies');
138
+ console.log(' mdboard preset create <name> Create a custom preset\n');
139
+ }
140
+
141
+ /**
142
+ * Recursively create directories for hierarchy entities.
143
+ */
144
+ function createHierarchyDirs(basePath, hierarchy) {
145
+ for (const [type, def] of Object.entries(hierarchy)) {
146
+ fs.mkdirSync(path.join(basePath, def.dir), { recursive: true });
147
+ // Don't recurse into children dirs here — they get created
148
+ // inside entity instance dirs when entities are created via CLI
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Collect directory names for output display.
154
+ */
155
+ function collectDirs(hierarchy, standalone, archive) {
156
+ const dirs = [];
157
+
158
+ function walkHierarchy(node) {
159
+ for (const [type, def] of Object.entries(node)) {
160
+ dirs.push(def.dir);
121
161
  }
122
- `);
162
+ }
163
+
164
+ walkHierarchy(hierarchy);
165
+
166
+ for (const [type, def] of Object.entries(standalone || {})) {
167
+ dirs.push(def.dir);
168
+ }
169
+
170
+ if (archive && archive.dir) {
171
+ dirs.push(archive.dir);
172
+ }
173
+
174
+ return dirs;
123
175
  }