@context-os/cli 1.0.0 → 1.6.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.
Files changed (27) hide show
  1. package/dist/commands/dashboard.js +68 -0
  2. package/dist/commands/graph.js +41 -0
  3. package/dist/commands/index-cmd.js +18 -0
  4. package/dist/commands/search.js +41 -0
  5. package/dist/commands/sync.js +25 -0
  6. package/dist/commands/validate.js +32 -0
  7. package/dist/commands/watch.js +38 -0
  8. package/dist/{workspace-cli/src/index.js → index.js} +9 -1
  9. package/dist/{workspace-cli/src/tests → tests}/smoke.test.js +17 -2
  10. package/package.json +6 -4
  11. package/templates/dashboard/index.html +454 -0
  12. package/dist/packages/core/src/index.js +0 -88
  13. package/dist/workspace-cli/src/commands/search.js +0 -33
  14. package/dist/workspace-cli/src/commands/sync.js +0 -43
  15. package/dist/workspace-cli/src/commands/validate.js +0 -136
  16. /package/dist/{workspace-cli/src/commands → commands}/archive.js +0 -0
  17. /package/dist/{workspace-cli/src/commands → commands}/context.js +0 -0
  18. /package/dist/{workspace-cli/src/commands → commands}/decide.js +0 -0
  19. /package/dist/{workspace-cli/src/commands → commands}/extract.js +0 -0
  20. /package/dist/{workspace-cli/src/commands → commands}/health.js +0 -0
  21. /package/dist/{workspace-cli/src/commands → commands}/init.js +0 -0
  22. /package/dist/{workspace-cli/src/commands → commands}/prune.js +0 -0
  23. /package/dist/{workspace-cli/src/commands → commands}/status.js +0 -0
  24. /package/dist/{workspace-cli/src/commands → commands}/summary.js +0 -0
  25. /package/dist/{workspace-cli/src/commands → commands}/tag.js +0 -0
  26. /package/dist/{workspace-cli/src/commands → commands}/today.js +0 -0
  27. /package/dist/{workspace-cli/src/utils.js → utils.js} +0 -0
@@ -0,0 +1,68 @@
1
+ import chalk from "chalk";
2
+ import http from "node:http";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { exec } from "node:child_process";
7
+ import { samplingService, knowledgeGraphService } from "@context-os/core";
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ export function dashboardCommand(program) {
10
+ program
11
+ .command("dashboard")
12
+ .description("Launch the Visual Control Center (Web UI)")
13
+ .option("-p, --port <number>", "Port to run the dashboard on", "3010")
14
+ .action(async (options) => {
15
+ const port = parseInt(options.port);
16
+ // Determine the template path
17
+ // In dev: src/commands/../../templates/dashboard/index.html
18
+ // In dist: dist/commands/../../templates/dashboard/index.html
19
+ const templatePath = path.resolve(__dirname, "../../templates/dashboard/index.html");
20
+ if (!fs.existsSync(templatePath)) {
21
+ // Fallback if structure varies
22
+ const fallbackPath = path.resolve(process.cwd(), "templates/dashboard/index.html");
23
+ if (!fs.existsSync(fallbackPath)) {
24
+ console.error(chalk.red(`\n❌ Error: Dashboard template not found at ${templatePath}`));
25
+ process.exit(1);
26
+ }
27
+ }
28
+ const server = http.createServer(async (req, res) => {
29
+ const url = req.url || "/";
30
+ // API endpoints
31
+ if (url === "/api/pulse") {
32
+ const pulse = await samplingService.getPulse();
33
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
34
+ return res.end(JSON.stringify(pulse));
35
+ }
36
+ if (url === "/api/graph") {
37
+ const graph = await knowledgeGraphService.getGraph();
38
+ res.writeHead(200, { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" });
39
+ return res.end(JSON.stringify(graph));
40
+ }
41
+ // Static Assets (Dashboard UI)
42
+ if (url === "/" || url === "/index.html") {
43
+ try {
44
+ const html = fs.readFileSync(templatePath, "utf8");
45
+ res.writeHead(200, { "Content-Type": "text/html" });
46
+ return res.end(html);
47
+ }
48
+ catch (e) {
49
+ res.writeHead(500);
50
+ return res.end("Internal Server Error Loading Dashboard");
51
+ }
52
+ }
53
+ res.writeHead(404);
54
+ res.end("Not Found");
55
+ });
56
+ server.listen(port, () => {
57
+ const dashboardUrl = `http://localhost:${port}`;
58
+ console.log(chalk.bold.cyan("\n🚀 ContextOS Visual Control Center"));
59
+ console.log(chalk.dim("------------------------------------------"));
60
+ console.log(chalk.green(` URL: ${dashboardUrl}`));
61
+ console.log(chalk.dim(" Status: Monitoring Workspace..."));
62
+ console.log(chalk.dim(" Cmd: Press Ctrl+C to stop server\n"));
63
+ // Launch Browser
64
+ const start = (process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open');
65
+ exec(`${start} ${dashboardUrl}`);
66
+ });
67
+ });
68
+ }
@@ -0,0 +1,41 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { knowledgeGraphService } from "@context-os/core";
4
+ export function graphCommand(program) {
5
+ program
6
+ .command("graph")
7
+ .description("Visualize the workspace knowledge graph (explicit + semantic links)")
8
+ .action(async () => {
9
+ const spinner = ora(`Building workspace graph...`).start();
10
+ try {
11
+ const graph = await knowledgeGraphService.getGraph();
12
+ if (graph.nodes.length === 0) {
13
+ spinner.info(chalk.yellow("Graph is empty. Index some files first."));
14
+ return;
15
+ }
16
+ spinner.succeed(chalk.green(`Federated Knowledge Graph (${graph.nodes.length} nodes, ${graph.edges.length} edges):`));
17
+ console.log("");
18
+ // 1. Group by entity type
19
+ const docs = graph.nodes.filter(n => n.type === 'document');
20
+ const tags = graph.nodes.filter(n => n.type === 'tag');
21
+ console.log(chalk.bold.blue("--- Documents ---"));
22
+ docs.forEach(doc => {
23
+ const connections = graph.edges.filter(e => e.source === doc.id || e.target === doc.id);
24
+ const semanticCount = connections.filter(e => e.type === 'semantic').length;
25
+ const linkCount = connections.length - semanticCount;
26
+ console.log(`${chalk.cyan(doc.id)} ${chalk.gray(`(${linkCount} links, ${semanticCount} semantic bridges)`)}`);
27
+ });
28
+ console.log("");
29
+ console.log(chalk.bold.yellow("--- Top Themes (#tags) ---"));
30
+ tags.forEach(tag => {
31
+ const usage = graph.edges.filter(e => e.target === tag.id).length;
32
+ console.log(`${chalk.yellow(tag.label)} ${chalk.gray(`(used in ${usage} files)`)}`);
33
+ });
34
+ console.log("");
35
+ console.log(chalk.dim("Run `context search <query>` to explore semantic neighborhoods."));
36
+ }
37
+ catch (error) {
38
+ spinner.fail(chalk.red(`Graph generation failed: ${error.message}`));
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,18 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { globalIndexer } from "@context-os/core";
4
+ export function indexCommand(program) {
5
+ program
6
+ .command("index")
7
+ .description("Rebuild the workspace intelligence index")
8
+ .action(async () => {
9
+ const spinner = ora("Indexing workspace context...").start();
10
+ try {
11
+ const index = await globalIndexer.reindex();
12
+ spinner.succeed(chalk.green(`Index rebuilt: ${index.records.length} files processed.`));
13
+ }
14
+ catch (error) {
15
+ spinner.fail(chalk.red(`Indexing failed: ${error.message}`));
16
+ }
17
+ });
18
+ }
@@ -0,0 +1,41 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { intelligenceService } from "@context-os/core";
4
+ export function searchCommand(program) {
5
+ program
6
+ .command("search")
7
+ .description("Search across the workspace (using metadata index)")
8
+ .argument("<query>", "Search query string")
9
+ .option("--deep", "Force deep scan using grep")
10
+ .action(async (query, options) => {
11
+ const spinner = ora(`Searching for ${chalk.cyan(query)}...`).start();
12
+ try {
13
+ const results = await intelligenceService.search(query, { deep: options.deep });
14
+ if (results.length === 0) {
15
+ spinner.info(chalk.yellow("No results found."));
16
+ return;
17
+ }
18
+ spinner.succeed(chalk.green(`Found ${results.length} matches:`));
19
+ console.log("");
20
+ results.forEach(res => {
21
+ let typeTag = chalk.cyan('[Index]');
22
+ if (res.type === 'deep')
23
+ typeTag = chalk.magenta('[Deep]');
24
+ if (res.type === 'semantic')
25
+ typeTag = chalk.bold.green('[Semantic]');
26
+ if (res.type === 'hybrid')
27
+ typeTag = chalk.bold.yellow('[Hybrid]');
28
+ const scoreDisplay = res.score ? chalk.green(` (${res.score.toFixed(2)})`) : '';
29
+ console.log(`${typeTag}${scoreDisplay} ${chalk.blue(res.path)}`);
30
+ if (res.title && res.title !== 'Deep Scan Result') {
31
+ console.log(`${chalk.gray(res.title)} ${res.tags.length ? chalk.yellow(`[${res.tags.join(', ')}]`) : ''}`);
32
+ }
33
+ console.log(`${chalk.white(res.excerpt)}...`);
34
+ console.log(chalk.gray('---'));
35
+ });
36
+ }
37
+ catch (error) {
38
+ spinner.fail(chalk.red(`Search failed: ${error.message}`));
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,25 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { workspaceService } from "@context-os/core";
4
+ export function syncCommand(program) {
5
+ program
6
+ .command("sync")
7
+ .description("Sync memory, changelog, and daily logs")
8
+ .argument("[project]", "Project name to sync context for")
9
+ .option("--force", "Force full re-index of the workspace")
10
+ .action(async (project, options) => {
11
+ const spinner = ora("Syncing workspace context...").start();
12
+ try {
13
+ const result = await workspaceService.sync(project, { force: options.force });
14
+ if (result.success) {
15
+ spinner.succeed(chalk.green(result.message));
16
+ }
17
+ else {
18
+ spinner.fail(chalk.red(result.message));
19
+ }
20
+ }
21
+ catch (error) {
22
+ spinner.fail(chalk.red(`Sync failed: ${error.message}`));
23
+ }
24
+ });
25
+ }
@@ -0,0 +1,32 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { validationService } from "@context-os/core";
4
+ export function validateCommand(program) {
5
+ program
6
+ .command("validate")
7
+ .description("Validate workspace files against JSON schemas")
8
+ .action(async () => {
9
+ const spinner = ora("Validating workspace integrity...").start();
10
+ try {
11
+ const result = await validationService.validateWorkspace();
12
+ if (result.valid) {
13
+ spinner.succeed(chalk.green("Workspace validation successful! All files conform to schema."));
14
+ }
15
+ else {
16
+ spinner.fail(chalk.red(`Workspace validation failed with ${result.issues.length} issues.`));
17
+ console.log("");
18
+ result.issues.forEach(issue => {
19
+ console.log(chalk.red(`\n❌ ${issue.message} in ${issue.project}/${issue.file}`));
20
+ if (issue.details) {
21
+ // Optional: console.log(chalk.yellow(` - detail: ${JSON.stringify(issue.details)}`));
22
+ }
23
+ });
24
+ process.exit(1);
25
+ }
26
+ }
27
+ catch (error) {
28
+ spinner.fail(chalk.red(`Validation error: ${error.message}`));
29
+ process.exit(1);
30
+ }
31
+ });
32
+ }
@@ -0,0 +1,38 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { watchService, globalIndexer, samplingService } from "@context-os/core";
4
+ export function watchCommand(program) {
5
+ program
6
+ .command("watch")
7
+ .description("Start real-time workspace monitoring and intelligence sync")
8
+ .action(async () => {
9
+ console.log(chalk.bold.cyan("\n📡 ContextOS Intelligence Monitor v1.5.0"));
10
+ console.log(chalk.dim("Press Ctrl+C to stop monitoring\n"));
11
+ const spinner = ora("Initializing workspace sweep...").start();
12
+ try {
13
+ // 1. Initial Incremental Sweep (as approved in plan)
14
+ await globalIndexer.reindex();
15
+ spinner.succeed("Initial sweep complete. Workspace is synchronized.");
16
+ // 2. Start Watch Service
17
+ watchService.start();
18
+ const pulse = await samplingService.getPulse();
19
+ console.log(chalk.green(`\n✅ Intelligence Layer Active`));
20
+ console.log(chalk.dim(` - Health Score: ${pulse.healthScore}%`));
21
+ console.log(chalk.dim(` - Nodes Mapped: ${pulse.recentChanges.length}+`));
22
+ console.log("");
23
+ const statusSpinner = ora("Monitoring for changes...").start();
24
+ // 3. Handle graceful shutdown
25
+ process.on("SIGINT", () => {
26
+ statusSpinner.stop();
27
+ watchService.stop();
28
+ console.log(chalk.yellow("\n👋 Watch service stopped. Workspace remains indexed."));
29
+ process.exit(0);
30
+ });
31
+ }
32
+ catch (error) {
33
+ spinner.fail("Failed to start watch service.");
34
+ console.error(chalk.red(error));
35
+ process.exit(1);
36
+ }
37
+ });
38
+ }
@@ -14,11 +14,15 @@ import { healthCommand } from "./commands/health.js";
14
14
  import { extractCommand } from "./commands/extract.js";
15
15
  import { tagCommand } from "./commands/tag.js";
16
16
  import { validateCommand } from "./commands/validate.js";
17
+ import { indexCommand } from "./commands/index-cmd.js";
18
+ import { graphCommand } from "./commands/graph.js";
19
+ import { watchCommand } from "./commands/watch.js";
20
+ import { dashboardCommand } from "./commands/dashboard.js";
17
21
  const program = new Command();
18
22
  program
19
23
  .name("workspace")
20
24
  .description("ContextOS Developer Interface Layer")
21
- .version("1.0.0");
25
+ .version("1.1.0");
22
26
  // Register Commands
23
27
  initCommand(program);
24
28
  todayCommand(program);
@@ -34,6 +38,10 @@ healthCommand(program);
34
38
  extractCommand(program);
35
39
  tagCommand(program);
36
40
  validateCommand(program);
41
+ indexCommand(program);
42
+ graphCommand(program);
43
+ watchCommand(program);
44
+ dashboardCommand(program);
37
45
  program.parse(process.argv);
38
46
  if (!process.argv.slice(2).length) {
39
47
  program.outputHelp();
@@ -21,8 +21,23 @@ describe("CLI Experience Layer (Smoke Tests)", () => {
21
21
  assert.ok(output.includes(cmd));
22
22
  });
23
23
  });
24
- it("should report version 1.0.0", () => {
24
+ it("should report version 1.1.0", () => {
25
25
  const output = execSync(`node ${cliPath} --version`).toString();
26
- assert.strictEqual(output.trim(), "1.0.0");
26
+ assert.strictEqual(output.trim(), "1.1.0");
27
+ });
28
+ describe("Functional Commands", () => {
29
+ it("should perform a workspace sync", () => {
30
+ const output = execSync(`node ${cliPath} sync 2>&1`).toString();
31
+ assert.ok(output.includes("Workspace indexed"), "Output should contain success message");
32
+ });
33
+ it("should search for indexed content", () => {
34
+ const output = execSync(`node ${cliPath} search "ContextOS" 2>&1`).toString();
35
+ assert.ok(output.includes("Found"), "Output should list search results");
36
+ assert.ok(output.includes("ContextOS"), "Output should contain 'ContextOS'");
37
+ });
38
+ it("should validate the workspace root", () => {
39
+ const output = execSync(`node ${cliPath} validate 2>&1`).toString();
40
+ assert.ok(output.includes("successful"), "Output should contain validation report");
41
+ });
27
42
  });
28
43
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-os/cli",
3
- "version": "1.0.0",
3
+ "version": "1.6.1",
4
4
  "description": "ContextOS Developer Interface Layer",
5
5
  "keywords": [
6
6
  "context-os",
@@ -26,10 +26,12 @@
26
26
  "node": ">=18"
27
27
  },
28
28
  "scripts": {
29
- "build": "tsc && node ../scripts/sync-templates.js",
29
+ "build": "tsc",
30
+ "postbuild": "node ../scripts/sync-templates.js",
30
31
  "watch": "tsc -w",
31
32
  "start": "node dist/index.js",
32
- "test": "npm run build && mocha dist/tests/**/*.test.js"
33
+ "test": "mocha dist/tests/**/*.test.js",
34
+ "validate": "node dist/index.js validate"
33
35
  },
34
36
  "dependencies": {
35
37
  "ajv": "^8.18.0",
@@ -40,7 +42,7 @@
40
42
  "front-matter": "^4.0.2",
41
43
  "fs-extra": "^11.2.0",
42
44
  "ora": "^8.0.1",
43
- "@context-os/core": "1.0.0"
45
+ "@context-os/core": "1.6.1"
44
46
  },
45
47
  "devDependencies": {
46
48
  "@types/chai": "^5.2.3",
@@ -0,0 +1,454 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ContextOS | Visual Control Center</title>
7
+ <!-- Premium CDNs -->
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Space+Grotesk:wght@300;500;700&display=swap" rel="stylesheet">
11
+ <script src="https://unpkg.com/3d-force-graph"></script>
12
+ <script src="https://unpkg.com/force-graph"></script>
13
+ <script src="https://unpkg.com/lucide@latest"></script>
14
+ <style>
15
+ :root {
16
+ --bg: #0c1324;
17
+ --surface: rgba(12, 19, 36, 0.6);
18
+ --surface-bright: rgba(25, 31, 49, 0.8);
19
+ --primary: #00f0ff;
20
+ --secondary: #a855f7;
21
+ --text: #dce1fb;
22
+ --text-dim: #849495;
23
+ --accent: #ff0055;
24
+ --glow: 0 0 20px rgba(0, 240, 255, 0.3);
25
+ --font-display: 'Space Grotesk', sans-serif;
26
+ --font-body: 'Inter', sans-serif;
27
+ }
28
+
29
+ body, html {
30
+ margin: 0;
31
+ padding: 0;
32
+ width: 100%;
33
+ height: 100%;
34
+ background: var(--bg);
35
+ color: var(--text);
36
+ font-family: var(--font-body);
37
+ overflow: hidden;
38
+ background-image:
39
+ radial-gradient(circle at 50% -20%, #1a2a44 0%, transparent 50%),
40
+ radial-gradient(circle at -10% 50%, #151b2d 0%, transparent 40%);
41
+ }
42
+
43
+ /* Scanline Effect */
44
+ body::before {
45
+ content: " ";
46
+ display: block;
47
+ position: absolute;
48
+ top: 0; left: 0; bottom: 0; right: 0;
49
+ background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
50
+ z-index: 100;
51
+ background-size: 100% 2px, 3px 100%;
52
+ pointer-events: none;
53
+ opacity: 0.15;
54
+ }
55
+
56
+ /* Glassmorphic UI Overlay */
57
+ #ui-layer {
58
+ position: absolute;
59
+ top: 0; left: 0; width: 100%; height: 100%;
60
+ pointer-events: none;
61
+ z-index: 10;
62
+ display: grid;
63
+ grid-template-areas:
64
+ "header header header"
65
+ "sidebar content inspector"
66
+ "footer footer footer";
67
+ grid-template-rows: auto 1fr auto;
68
+ grid-template-columns: 320px 1fr 320px;
69
+ padding: 24px;
70
+ box-sizing: border-box;
71
+ gap: 20px;
72
+ }
73
+
74
+ .hud-card {
75
+ background: var(--surface);
76
+ backdrop-filter: blur(20px);
77
+ border: 1px solid rgba(132, 148, 149, 0.15);
78
+ border-radius: 12px;
79
+ padding: 20px;
80
+ pointer-events: auto;
81
+ box-shadow: 0 8px 32px rgba(0,0,0,0.4);
82
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
83
+ }
84
+
85
+ .hud-card:hover {
86
+ border-color: rgba(0, 240, 255, 0.4);
87
+ background: var(--surface-bright);
88
+ transform: translateY(-2px);
89
+ }
90
+
91
+ header {
92
+ grid-area: header;
93
+ display: flex;
94
+ justify-content: space-between;
95
+ align-items: center;
96
+ }
97
+
98
+ #brand h1 {
99
+ font-family: var(--font-display);
100
+ margin: 0;
101
+ font-size: 24px;
102
+ font-weight: 700;
103
+ letter-spacing: 3px;
104
+ text-transform: uppercase;
105
+ color: var(--primary);
106
+ text-shadow: 0 0 15px rgba(0, 240, 255, 0.5);
107
+ }
108
+
109
+ .health-gauge {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 20px;
113
+ }
114
+
115
+ /* Sidebar & Inspector */
116
+ #sidebar { grid-area: sidebar; display: flex; flex-direction: column; gap: 20px; }
117
+ #inspector {
118
+ grid-area: inspector;
119
+ display: flex;
120
+ flex-direction: column;
121
+ gap: 20px;
122
+ transform: translateX(350px);
123
+ transition: transform 0.5s ease;
124
+ }
125
+ #inspector.active { transform: translateX(0); }
126
+
127
+ .stat-label {
128
+ font-family: var(--font-display);
129
+ font-size: 11px;
130
+ text-transform: uppercase;
131
+ color: var(--primary);
132
+ letter-spacing: 2px;
133
+ margin-bottom: 15px;
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 8px;
137
+ opacity: 0.8;
138
+ }
139
+
140
+ .tag-pill {
141
+ display: inline-block;
142
+ padding: 5px 12px;
143
+ background: rgba(168, 85, 247, 0.1);
144
+ border: 1px solid rgba(168, 85, 247, 0.3);
145
+ border-radius: 20px;
146
+ font-size: 11px;
147
+ margin: 0 6px 6px 0;
148
+ color: #d8b4ff;
149
+ cursor: pointer;
150
+ }
151
+
152
+ .tag-pill:hover { background: rgba(168, 85, 247, 0.3); }
153
+
154
+ /* Bottom Status Bar */
155
+ footer {
156
+ grid-area: footer;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: space-between;
160
+ }
161
+
162
+ .ticker-text {
163
+ font-family: var(--font-display);
164
+ font-size: 10px;
165
+ color: var(--primary);
166
+ white-space: nowrap;
167
+ overflow: hidden;
168
+ width: 300px;
169
+ letter-spacing: 1px;
170
+ }
171
+
172
+ /* 3D Graph Container */
173
+ #graph-container {
174
+ position: absolute;
175
+ top: 0; left: 0; width: 100%; height: 100%;
176
+ z-index: 1;
177
+ }
178
+
179
+ .node-tooltip {
180
+ background: rgba(12, 19, 36, 0.95);
181
+ border: 1px solid var(--primary);
182
+ padding: 12px;
183
+ border-radius: 8px;
184
+ backdrop-filter: blur(10px);
185
+ font-family: var(--font-body);
186
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
187
+ }
188
+
189
+ .pulse-dot {
190
+ width: 8px;
191
+ height: 8px;
192
+ border-radius: 50%;
193
+ background: var(--primary);
194
+ box-shadow: 0 0 10px var(--primary);
195
+ animation: pulse 2s infinite;
196
+ }
197
+
198
+ @keyframes pulse {
199
+ 0% { opacity: 1; transform: scale(1); }
200
+ 50% { opacity: 0.5; transform: scale(0.8); }
201
+ 100% { opacity: 1; transform: scale(1); }
202
+ }
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div id="graph-container"></div>
207
+
208
+ <div id="ui-layer">
209
+ <header>
210
+ <div class="hud-card" id="brand">
211
+ <i data-lucide="brain-circuit" style="color: var(--primary);"></i>
212
+ <div>
213
+ <h1>ContextOS</h1>
214
+ <div style="font-size: 10px; color: var(--text-dim); letter-spacing: 1px;">Spatial Intelligence Protocol</div>
215
+ </div>
216
+ </div>
217
+
218
+ <div class="hud-card health-gauge">
219
+ <div style="text-align: right;">
220
+ <div class="stat-label" style="margin:0">Workspace Stability</div>
221
+ <div style="font-size: 18px; font-weight: 700; color: var(--primary); font-family: var(--font-display);" id="health-text">--%</div>
222
+ </div>
223
+ <div id="health-ring" style="width:40px; height:40px; border-width: 2px;">
224
+ <div id="health-value" style="font-size: 12px;">--</div>
225
+ </div>
226
+ </div>
227
+ </header>
228
+
229
+ <aside id="sidebar">
230
+ <div class="hud-card">
231
+ <div class="stat-label"><i data-lucide="zap" size="14"></i> Trending Context</div>
232
+ <div id="tag-container"></div>
233
+ </div>
234
+
235
+ <div class="hud-card" style="flex: 1; overflow: hidden; display: flex; flex-direction: column;">
236
+ <div class="stat-label"><i data-lucide="radio" size="14"></i> Live Activity</div>
237
+ <div id="activity-list" style="flex:1; overflow-y: auto; padding-right: 8px;"></div>
238
+ </div>
239
+ </aside>
240
+
241
+ <section id="inspector" class="hud-card">
242
+ <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px;">
243
+ <div class="stat-label" style="margin:0"><i data-lucide="info" size="14"></i> Node Inspector</div>
244
+ <button onclick="closeInspector()" style="background: none; border: none; color: var(--text-dim); cursor: pointer;"><i data-lucide="x" size="16"></i></button>
245
+ </div>
246
+
247
+ <div id="inspector-content">
248
+ <div style="text-align: center; padding: 40px 20px; opacity: 0.4;">
249
+ <i data-lucide="mouse-pointer-2" size="32" style="margin-bottom: 16px;"></i>
250
+ <p style="font-size: 12px;">Select an entity to reveal its spatial soul</p>
251
+ </div>
252
+ </div>
253
+ </section>
254
+
255
+ <footer>
256
+ <div class="hud-card" style="display: flex; align-items: center; gap: 15px; padding: 8px 16px;">
257
+ <div class="pulse-dot"></div>
258
+ <div class="ticker-text" id="system-ticker">INITIALIZING AETHER CORE... READY.</div>
259
+ <span id="timestamp" style="font-family: var(--font-display); font-size: 12px;">--:--:--</span>
260
+ </div>
261
+
262
+ <div class="hud-card" style="padding: 8px 16px; font-family: var(--font-display); letter-spacing: 1px; font-size: 10px;">
263
+ DECK: <span style="color: var(--primary)">V1.6.1-AETHER</span>
264
+ </div>
265
+ </footer>
266
+ </div>
267
+
268
+ <script>
269
+ lucide.createIcons();
270
+
271
+ let Graph;
272
+ let selectedNode = null;
273
+
274
+ async function init() {
275
+ console.log("Initializing Spatial Engine...");
276
+ const container = document.getElementById('graph-container');
277
+
278
+ try {
279
+ if (window.ForceGraph3D) {
280
+ Graph = ForceGraph3D()(container)
281
+ .backgroundColor('rgba(0,0,0,0)') // Use CSS background
282
+ .showNavInfo(false)
283
+ .nodeLabel(null) // Custom tooltips instead
284
+ .nodeAutoColorBy('type')
285
+ .linkWidth(1)
286
+ .linkColor(() => 'rgba(255,255,255,0.1)')
287
+ .linkDirectionalParticles(2)
288
+ .linkDirectionalParticleSpeed(d => (d.weight || 1) * 0.005)
289
+ .linkDirectionalParticleWidth(2)
290
+ .onNodeClick(node => inspectNode(node))
291
+ .onBackgroundClick(() => closeInspector());
292
+
293
+ // Aether Custom Geometries
294
+ Graph.nodeThreeObject(node => {
295
+ let geometry;
296
+ let color = node.type === 'tag' ? '#a855f7' : '#00f0ff';
297
+
298
+ if (node.type === 'tag') {
299
+ geometry = new THREE.TetrahedronGeometry(8);
300
+ } else if (node.type === 'person') {
301
+ geometry = new THREE.OctahedronGeometry(10);
302
+ color = '#ffcc00';
303
+ } else {
304
+ geometry = new THREE.SphereGeometry(12, 16, 16);
305
+ }
306
+
307
+ const material = new THREE.MeshPhongMaterial({
308
+ color: color,
309
+ transparent: true,
310
+ opacity: 0.9,
311
+ shininess: 100,
312
+ emissive: color,
313
+ emissiveIntensity: 0.5
314
+ });
315
+
316
+ return new THREE.Mesh(geometry, material);
317
+ });
318
+
319
+ // Lighting & Atmosphere
320
+ const scene = Graph.scene();
321
+ const ambient = new THREE.AmbientLight(0xffffff, 0.4);
322
+ const point = new THREE.PointLight(0x00f0ff, 1, 500);
323
+ point.position.set(100, 100, 100);
324
+ scene.add(ambient);
325
+ scene.add(point);
326
+
327
+ // Auto-Orbit
328
+ let angle = 0;
329
+ setInterval(() => {
330
+ if (!selectedNode) {
331
+ angle += 0.001;
332
+ Graph.cameraPosition({
333
+ x: 400 * Math.sin(angle),
334
+ z: 400 * Math.cos(angle)
335
+ });
336
+ }
337
+ }, 50);
338
+ }
339
+ } catch (e) {
340
+ console.warn("Aether 3D Engine failed, engaging 2D fallback:", e);
341
+ container.innerHTML = '';
342
+ Graph = ForceGraph()(container)
343
+ .backgroundColor('rgba(0,0,0,0)')
344
+ .nodeLabel(node => `<div class="node-tooltip"><b>${node.label}</b><br/>${node.id}</div>`)
345
+ .nodeAutoColorBy('type')
346
+ .linkDirectionalParticles(2)
347
+ .linkDirectionalParticleSpeed(d => (d.weight || 1) * 0.01)
348
+ .onNodeClick(node => inspectNode(node));
349
+ }
350
+
351
+ await updateData();
352
+ setInterval(updateData, 5000);
353
+ }
354
+
355
+ function inspectNode(node) {
356
+ selectedNode = node;
357
+ const inspector = document.getElementById('inspector');
358
+ const content = document.getElementById('inspector-content');
359
+
360
+ inspector.classList.add('active');
361
+
362
+ content.innerHTML = `
363
+ <div style="padding: 10px;">
364
+ <div style="color: var(--primary); font-family: var(--font-display); font-size: 18px; margin-bottom: 5px;">${node.label}</div>
365
+ <div style="font-size: 11px; opacity: 0.5; margin-bottom: 20px; font-family: monospace;">${node.id}</div>
366
+
367
+ <div class="stat-label" style="font-size: 9px;">Intelligence Metadata</div>
368
+ <div class="hud-card" style="margin-bottom: 15px; background: rgba(0,0,0,0.2); padding: 12px;">
369
+ <div style="font-size: 12px; line-height: 1.6;">
370
+ ${node.metadata?.excerpt || 'Scanning for latent semantic patterns...'}
371
+ </div>
372
+ </div>
373
+
374
+ <div class="stat-label" style="font-size: 9px;">Entity Classification</div>
375
+ <div style="margin-bottom: 20px;">
376
+ <span class="tag-pill" style="border-color: var(--primary); color: var(--primary);">Type: ${node.type}</span>
377
+ <span class="tag-pill">Access: Federated</span>
378
+ </div>
379
+
380
+ <div style="display: flex; gap: 10px;">
381
+ <button class="hud-card" style="flex:1; padding: 10px; font-size: 11px; text-transform: uppercase; cursor: pointer;" onclick="copyPath('${node.id}')">
382
+ <i data-lucide="copy" size="12"></i> Copy Path
383
+ </button>
384
+ </div>
385
+ </div>
386
+ `;
387
+ lucide.createIcons();
388
+
389
+ // Focus camera in 3D
390
+ if (Graph && Graph.cameraPosition) {
391
+ const distance = 150;
392
+ const distRatio = 1 + distance/Math.hypot(node.x, node.y, node.z);
393
+ Graph.cameraPosition(
394
+ { x: node.x * distRatio, y: node.y * distRatio, z: node.z * distRatio },
395
+ node,
396
+ 2000
397
+ );
398
+ }
399
+ }
400
+
401
+ function closeInspector() {
402
+ selectedNode = null;
403
+ document.getElementById('inspector').classList.remove('active');
404
+ }
405
+
406
+ function copyPath(id) {
407
+ navigator.clipboard.writeText(id);
408
+ const ticker = document.getElementById('system-ticker');
409
+ ticker.textContent = `SYSTEM CLASSIFIER: PATH COPIED [${id}]`;
410
+ setTimeout(() => { ticker.textContent = "IDLE MONITORS ACTIVE... STANDBY."; }, 3000);
411
+ }
412
+
413
+ async function updateData() {
414
+ try {
415
+ const [graphRes, pulseRes] = await Promise.all([
416
+ fetch('/api/graph').then(r => r.json()),
417
+ fetch('/api/pulse').then(r => r.json())
418
+ ]);
419
+
420
+ Graph.graphData(graphRes);
421
+
422
+ document.getElementById('health-text').textContent = `${pulseRes.healthScore}%`;
423
+ document.getElementById('health-value').textContent = pulseRes.healthScore;
424
+ document.getElementById('timestamp').textContent = new Date(pulseRes.timestamp).toLocaleTimeString();
425
+
426
+ // Ticker logic
427
+ if (pulseRes.recentChanges.length > 0) {
428
+ document.getElementById('system-ticker').textContent = `PULSE DETECTED: SYNCING [${pulseRes.recentChanges[0]}]`;
429
+ }
430
+
431
+ const tagContainer = document.getElementById('tag-container');
432
+ tagContainer.innerHTML = pulseRes.topTags.map(tag =>
433
+ `<span class="tag-pill">#${tag}</span>`
434
+ ).join('');
435
+
436
+ const activityList = document.getElementById('activity-list');
437
+ activityList.innerHTML = pulseRes.recentChanges.map(change =>
438
+ `<div class="hud-card" style="margin-bottom: 10px; padding: 12px; font-size: 11px; box-shadow: none;">
439
+ <div style="color: var(--primary); margin-bottom: 4px; font-family: var(--font-display); font-size: 9px; opacity: 0.6;">RECENT SYNC</div>
440
+ <div style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${change}</div>
441
+ </div>`
442
+ ).join('');
443
+
444
+ lucide.createIcons();
445
+ } catch (e) {
446
+ console.error("Aether Bridge Failure:", e);
447
+ document.getElementById('system-ticker').textContent = "ERROR: SPATIAL BRIDGE COLLAPSED. RETRYING...";
448
+ }
449
+ }
450
+
451
+ init();
452
+ </script>
453
+ </body>
454
+ </html>
@@ -1,88 +0,0 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs';
3
- import { spawn } from 'node:child_process';
4
- /**
5
- * Discovers the workspace root by looking for root/soul.md in parent directories.
6
- */
7
- export function findWorkspaceRoot() {
8
- let current = process.cwd();
9
- const root = path.parse(current).root;
10
- while (current !== root) {
11
- if (fs.existsSync(path.join(current, "root", "soul.md"))) {
12
- return fs.realpathSync(current);
13
- }
14
- current = path.dirname(current);
15
- }
16
- return fs.realpathSync(process.cwd()); // Fallback to CWD
17
- }
18
- export const workspaceRoot = findWorkspaceRoot();
19
- /**
20
- * Standard ContextOS "Buckets" for security isolation.
21
- */
22
- export const ALLOWED_BUCKETS = [
23
- "projects",
24
- "knowledge",
25
- "schemas",
26
- "archive",
27
- "log",
28
- "orgs",
29
- "root"
30
- ];
31
- /**
32
- * Validates that a path is within the workspace root and inside an allowed bucket.
33
- */
34
- export function validatePath(requestedPath) {
35
- const resolvedPath = path.resolve(workspaceRoot, requestedPath);
36
- let fullPath;
37
- try {
38
- fullPath = fs.realpathSync(resolvedPath);
39
- }
40
- catch (e) {
41
- fullPath = resolvedPath;
42
- }
43
- const relativePath = path.relative(workspaceRoot, fullPath);
44
- // Security check: must be within the workspace root
45
- if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
46
- throw new Error(`Security violation: Path ${requestedPath} is outside the allowed ContextOS workspace root.`);
47
- }
48
- // Enterprise check: must be within an allowed bucket
49
- const isAllowed = ALLOWED_BUCKETS.some(bucket => {
50
- const bucketRoot = path.join(workspaceRoot, bucket);
51
- const bucketRelative = path.relative(bucketRoot, fullPath);
52
- return !bucketRelative.startsWith("..") && !path.isAbsolute(bucketRelative);
53
- });
54
- if (!isAllowed) {
55
- throw new Error(`Security violation: Path ${requestedPath} is outside the allowed bucket (projects, orgs, knowledge, schemas, etc).`);
56
- }
57
- return { fullPath, relativePath };
58
- }
59
- /**
60
- * Checks if a path is in a read-only bucket for agents.
61
- */
62
- export function isReadOnly(filePath) {
63
- const { fullPath } = validatePath(filePath);
64
- const readOnlyBuckets = ["knowledge", "schemas", "root"];
65
- return readOnlyBuckets.some(bucket => {
66
- const bucketRoot = path.join(workspaceRoot, bucket);
67
- const bucketRelative = path.relative(bucketRoot, fullPath);
68
- return !bucketRelative.startsWith("..") && !path.isAbsolute(bucketRelative);
69
- });
70
- }
71
- /**
72
- * Executes an atomic git transaction (Add + Commit).
73
- */
74
- export async function gitCommit(filePath, message) {
75
- return new Promise((resolve, reject) => {
76
- const add = spawn("git", ["add", filePath], { cwd: workspaceRoot });
77
- add.on("close", (code) => {
78
- if (code !== 0 && code !== null) {
79
- return reject(new Error(`Git add failed with code ${code}`));
80
- }
81
- const commit = spawn("git", ["commit", "-m", message], { cwd: workspaceRoot });
82
- commit.on("close", (code) => {
83
- // If code is not 0, it might be "nothing to commit" which is fine for our tools
84
- resolve();
85
- });
86
- });
87
- });
88
- }
@@ -1,33 +0,0 @@
1
- import chalk from "chalk";
2
- import ora from "ora";
3
- import { exec } from "child_process";
4
- import { promisify } from "util";
5
- const execAsync = promisify(exec);
6
- export function searchCommand(program) {
7
- program
8
- .command("search")
9
- .description("Search across the workspace")
10
- .argument("<query>", "Search query string")
11
- .action(async (query) => {
12
- const spinner = ora(`Searching for ${chalk.cyan(query)}...`).start();
13
- try {
14
- const workspaceRoot = process.cwd();
15
- // Use grep -rnI as a robust default
16
- const command = `grep -rnIE "${query}" . | head -n 20`;
17
- const { stdout } = await execAsync(command, { cwd: workspaceRoot });
18
- if (!stdout) {
19
- spinner.info(chalk.yellow("No results found."));
20
- return;
21
- }
22
- spinner.succeed(chalk.green("Search results:"));
23
- console.log(`\n${stdout}`);
24
- }
25
- catch (error) {
26
- if (error.code === 1) {
27
- spinner.info(chalk.yellow("No results found."));
28
- return;
29
- }
30
- spinner.fail(chalk.red(`Search failed: ${error.message}`));
31
- }
32
- });
33
- }
@@ -1,43 +0,0 @@
1
- import chalk from "chalk";
2
- import ora from "ora";
3
- import fs from "fs-extra";
4
- import path from "path";
5
- export function syncCommand(program) {
6
- program
7
- .command("sync")
8
- .description("Sync memory, changelog, and daily logs")
9
- .argument("[project]", "Project name to sync context for")
10
- .action(async (project) => {
11
- const spinner = ora("Syncing workspace context...").start();
12
- try {
13
- const workspaceRoot = process.cwd();
14
- const date = new Date().toISOString().split("T")[0];
15
- // Simulating sync logic: ensuring memory.md has a "Last Sync" timestamp
16
- if (project) {
17
- const projectDir = path.join(workspaceRoot, "projects", project);
18
- const memoryPath = path.join(projectDir, "memory.md");
19
- if (await fs.pathExists(memoryPath)) {
20
- let content = await fs.readFile(memoryPath, "utf-8");
21
- const syncMark = `\n> [!NOTE]\n> Last Sync: ${date} ${new Date().toLocaleTimeString()}\n`;
22
- if (!content.includes("Last Sync:")) {
23
- await fs.appendFile(memoryPath, syncMark);
24
- }
25
- else {
26
- content = content.replace(/> \[!NOTE\]\n> Last Sync: .*/, syncMark.trim());
27
- await fs.writeFile(memoryPath, content);
28
- }
29
- spinner.succeed(chalk.green(`Synced memory for ${project}`));
30
- }
31
- else {
32
- spinner.fail(chalk.red(`Memory file not found for ${project}`));
33
- }
34
- }
35
- else {
36
- spinner.succeed(chalk.green("Global workspace sync complete."));
37
- }
38
- }
39
- catch (error) {
40
- spinner.fail(chalk.red(`Sync failed: ${error.message}`));
41
- }
42
- });
43
- }
@@ -1,136 +0,0 @@
1
- import chalk from "chalk";
2
- import ora from "ora";
3
- import fs from "fs-extra";
4
- import path from "path";
5
- import AjvModule from "ajv";
6
- import addFormatsModule from "ajv-formats";
7
- import fm from "front-matter";
8
- const Ajv = AjvModule.default || AjvModule;
9
- const addFormats = addFormatsModule.default || addFormatsModule;
10
- const ajv = new Ajv({ allErrors: true });
11
- addFormats(ajv);
12
- export function validateCommand(program) {
13
- program
14
- .command("validate")
15
- .description("Validate workspace files against JSON schemas")
16
- .action(async () => {
17
- const spinner = ora("Validating workspace integrity...").start();
18
- try {
19
- const findWorkspaceRoot = () => {
20
- let current = process.cwd();
21
- const root = "/";
22
- while (current !== root) {
23
- if (fs.existsSync(path.join(current, "root", "soul.md"))) {
24
- return current;
25
- }
26
- current = path.dirname(current);
27
- }
28
- return process.cwd();
29
- };
30
- const workspaceRoot = findWorkspaceRoot();
31
- const schemasDir = path.join(workspaceRoot, "schemas");
32
- const projectsDir = path.join(workspaceRoot, "projects");
33
- const starterDir = path.join(workspaceRoot, "workspace-starter");
34
- if (!(await fs.pathExists(schemasDir))) {
35
- spinner.fail(chalk.red(`Schemas directory not found at ${schemasDir}`));
36
- process.exit(1);
37
- }
38
- const validFiles = [
39
- { name: "SOUL.md", schema: "soul.schema.json", required: true },
40
- { name: "CONTEXT.md", schema: "context.schema.json", required: true },
41
- { name: "memory.md", schema: "memory.schema.json" },
42
- { name: "decisions.md", schema: "decision.schema.json" }
43
- ];
44
- let totalIssues = 0;
45
- // Find all project-like directories (projects/* and workspace-starter)
46
- const projectPaths = [];
47
- if (await fs.pathExists(projectsDir)) {
48
- const projects = await fs.readdir(projectsDir);
49
- for (const p of projects) {
50
- const fullPath = path.join(projectsDir, p);
51
- if ((await fs.stat(fullPath)).isDirectory()) {
52
- projectPaths.push(fullPath);
53
- }
54
- }
55
- }
56
- if (await fs.pathExists(starterDir)) {
57
- projectPaths.push(starterDir);
58
- }
59
- for (const projectPath of projectPaths) {
60
- const projectName = path.basename(projectPath);
61
- const filesInDir = await fs.readdir(projectPath);
62
- for (const config of validFiles) {
63
- // Case-insensitive search
64
- const fileName = filesInDir.find(f => f.toLowerCase() === config.name.toLowerCase());
65
- if (!fileName) {
66
- if (config.required) {
67
- totalIssues++;
68
- console.log(chalk.red(`\n❌ Missing required file in ${projectName}: ${config.name}`));
69
- }
70
- continue;
71
- }
72
- const filePath = path.join(projectPath, fileName);
73
- const schemaPath = path.join(schemasDir, config.schema);
74
- const schema = await fs.readJson(schemaPath);
75
- const validate = ajv.compile(schema);
76
- const content = await fs.readFile(filePath, "utf-8");
77
- const data = extractMetadata(content);
78
- const valid = validate(data);
79
- if (!valid) {
80
- totalIssues++;
81
- console.log(chalk.red(`\n❌ Schema error in ${projectName}/${fileName}:`));
82
- validate.errors?.forEach((err) => {
83
- console.log(chalk.yellow(` - ${err.instancePath || 'root'} ${err.message}`));
84
- });
85
- }
86
- }
87
- }
88
- if (totalIssues === 0) {
89
- spinner.succeed(chalk.green("Workspace validation successful! All files conform to schema."));
90
- }
91
- else {
92
- spinner.fail(chalk.red(`Workspace validation failed with ${totalIssues} issues.`));
93
- process.exit(1);
94
- }
95
- }
96
- catch (error) {
97
- spinner.fail(chalk.red(`Validation error: ${error.message}`));
98
- process.exit(1);
99
- }
100
- });
101
- }
102
- /**
103
- * Robustly extract metadata from a markdown file.
104
- * Prioritizes Frontmatter, falls back to Section mapping.
105
- */
106
- function extractMetadata(content) {
107
- let data = {};
108
- try {
109
- const parse = fm.default || fm;
110
- if (content.trim().startsWith("---")) {
111
- const parsed = parse(content);
112
- data = parsed.attributes || {};
113
- }
114
- }
115
- catch (e) {
116
- console.error(chalk.yellow(` ! Frontmatter parse failed, falling back to sections.`));
117
- }
118
- // SUPPLEMENT with section parsing if frontmatter is missing fields
119
- const sections = content.split(/^## /m).slice(1);
120
- sections.forEach(s => {
121
- const lines = s.split("\n");
122
- const title = lines[0].trim();
123
- const body = lines.slice(1).join("\n").trim();
124
- if (!data[title] || (Array.isArray(data[title]) && data[title].length === 0)) {
125
- if (["Core Principles", "Behavioral Rules", "Goals", "Capabilities", "Constraints", "Tags", "Active Tasks", "Backlog"].includes(title)) {
126
- data[title] = body.split("\n")
127
- .map(l => l.replace(/^[-*]\s*/, "").trim())
128
- .filter(l => l.length > 0);
129
- }
130
- else {
131
- data[title] = body;
132
- }
133
- }
134
- });
135
- return data;
136
- }
File without changes