@awareness-sdk/local 0.1.13 → 0.2.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/bin/awareness-local.mjs +16 -0
- package/package.json +1 -1
- package/src/core/cloud-sync.mjs +52 -0
- package/src/daemon.mjs +3 -0
- package/src/mcp-stdio.mjs +398 -0
- package/src/web/index.html +36 -0
package/bin/awareness-local.mjs
CHANGED
|
@@ -433,6 +433,17 @@ async function cmdReindex(flags) {
|
|
|
433
433
|
}
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Run as a stdio MCP server (for IDE integrations like Claude Code).
|
|
438
|
+
*/
|
|
439
|
+
async function cmdMcp(flags) {
|
|
440
|
+
const projectDir = resolveProjectDir(flags);
|
|
441
|
+
const port = resolvePort(flags);
|
|
442
|
+
|
|
443
|
+
const { startStdioMcp } = await import('../src/mcp-stdio.mjs');
|
|
444
|
+
await startStdioMcp({ port, projectDir });
|
|
445
|
+
}
|
|
446
|
+
|
|
436
447
|
// ---------------------------------------------------------------------------
|
|
437
448
|
// Help
|
|
438
449
|
// ---------------------------------------------------------------------------
|
|
@@ -449,6 +460,7 @@ Commands:
|
|
|
449
460
|
stop Stop the daemon
|
|
450
461
|
status Show daemon status and stats
|
|
451
462
|
reindex Rebuild the search index
|
|
463
|
+
mcp Run as stdio MCP server
|
|
452
464
|
|
|
453
465
|
Options:
|
|
454
466
|
--project <dir> Project directory (default: current directory)
|
|
@@ -461,6 +473,7 @@ Examples:
|
|
|
461
473
|
npx @awareness-sdk/local status
|
|
462
474
|
npx @awareness-sdk/local stop
|
|
463
475
|
npx @awareness-sdk/local reindex --project /path/to/project
|
|
476
|
+
npx @awareness-sdk/local mcp --project /path/to/project --port 37800
|
|
464
477
|
`);
|
|
465
478
|
}
|
|
466
479
|
|
|
@@ -489,6 +502,9 @@ async function main() {
|
|
|
489
502
|
case 'reindex':
|
|
490
503
|
await cmdReindex(flags);
|
|
491
504
|
break;
|
|
505
|
+
case 'mcp':
|
|
506
|
+
await cmdMcp(flags);
|
|
507
|
+
break;
|
|
492
508
|
default:
|
|
493
509
|
console.error(`Unknown command: ${command}`);
|
|
494
510
|
printHelp();
|
package/package.json
CHANGED
package/src/core/cloud-sync.mjs
CHANGED
|
@@ -375,6 +375,10 @@ export class CloudSync {
|
|
|
375
375
|
console.error(`${LOG_PREFIX} syncToCloud failed:`, err.message);
|
|
376
376
|
}
|
|
377
377
|
|
|
378
|
+
if (synced > 0) {
|
|
379
|
+
this._recordSyncEvent('memories', { count: synced, direction: 'push' });
|
|
380
|
+
}
|
|
381
|
+
|
|
378
382
|
return { synced, errors };
|
|
379
383
|
}
|
|
380
384
|
|
|
@@ -426,6 +430,7 @@ export class CloudSync {
|
|
|
426
430
|
|
|
427
431
|
if (pulled > 0) {
|
|
428
432
|
console.log(`${LOG_PREFIX} Pulled ${pulled} memories from cloud`);
|
|
433
|
+
this._recordSyncEvent('memories', { count: pulled, direction: 'pull' });
|
|
429
434
|
}
|
|
430
435
|
} catch (err) {
|
|
431
436
|
console.error(`${LOG_PREFIX} pullFromCloud failed:`, err.message);
|
|
@@ -496,6 +501,7 @@ export class CloudSync {
|
|
|
496
501
|
|
|
497
502
|
if (synced > 0) {
|
|
498
503
|
console.log(`${LOG_PREFIX} Pushed ${synced} knowledge cards to cloud` + (errors ? ` (${errors} errors)` : ''));
|
|
504
|
+
this._recordSyncEvent('insights', { count: synced, direction: 'push' });
|
|
499
505
|
}
|
|
500
506
|
} catch (err) {
|
|
501
507
|
console.error(`${LOG_PREFIX} syncInsightsToCloud failed:`, err.message);
|
|
@@ -563,6 +569,7 @@ export class CloudSync {
|
|
|
563
569
|
|
|
564
570
|
if (synced > 0) {
|
|
565
571
|
console.log(`${LOG_PREFIX} Pushed ${synced} tasks to cloud` + (errors ? ` (${errors} errors)` : ''));
|
|
572
|
+
this._recordSyncEvent('tasks', { count: synced, direction: 'push' });
|
|
566
573
|
}
|
|
567
574
|
} catch (err) {
|
|
568
575
|
console.error(`${LOG_PREFIX} syncTasksToCloud failed:`, err.message);
|
|
@@ -1171,6 +1178,51 @@ export class CloudSync {
|
|
|
1171
1178
|
}
|
|
1172
1179
|
}
|
|
1173
1180
|
|
|
1181
|
+
// =========================================================================
|
|
1182
|
+
// Public — sync history
|
|
1183
|
+
// =========================================================================
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Record a sync event to the sync_state table for history tracking.
|
|
1187
|
+
* @param {string} type — "memories" | "insights" | "tasks"
|
|
1188
|
+
* @param {object} details — { count, direction: "push"|"pull" }
|
|
1189
|
+
*/
|
|
1190
|
+
_recordSyncEvent(type, details) {
|
|
1191
|
+
try {
|
|
1192
|
+
const timestamp = new Date().toISOString();
|
|
1193
|
+
const key = `sync_log:${timestamp}`;
|
|
1194
|
+
const value = JSON.stringify({ type, details, timestamp });
|
|
1195
|
+
this._setSyncState(key, value);
|
|
1196
|
+
} catch {
|
|
1197
|
+
// Non-critical — don't crash on history logging failure
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Get recent sync history events.
|
|
1203
|
+
* @param {number} [limit=20] — Maximum number of events to return
|
|
1204
|
+
* @returns {Array<{ type: string, details: object, timestamp: string }>}
|
|
1205
|
+
*/
|
|
1206
|
+
getSyncHistory(limit = 20) {
|
|
1207
|
+
try {
|
|
1208
|
+
const rows = this.indexer.db
|
|
1209
|
+
.prepare(
|
|
1210
|
+
`SELECT value FROM sync_state WHERE key LIKE 'sync_log:%' ORDER BY key DESC LIMIT ?`
|
|
1211
|
+
)
|
|
1212
|
+
.all(limit);
|
|
1213
|
+
|
|
1214
|
+
return rows.map((row) => {
|
|
1215
|
+
try {
|
|
1216
|
+
return JSON.parse(row.value);
|
|
1217
|
+
} catch {
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
}).filter(Boolean);
|
|
1221
|
+
} catch {
|
|
1222
|
+
return [];
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1174
1226
|
// =========================================================================
|
|
1175
1227
|
// Internal — utilities
|
|
1176
1228
|
// =========================================================================
|
package/src/daemon.mjs
CHANGED
|
@@ -1072,6 +1072,8 @@ export class AwarenessLocalDaemon {
|
|
|
1072
1072
|
const config = this._loadConfig();
|
|
1073
1073
|
const cloud = config.cloud || {};
|
|
1074
1074
|
|
|
1075
|
+
const history = this.cloudSync ? this.cloudSync.getSyncHistory() : [];
|
|
1076
|
+
|
|
1075
1077
|
return jsonResponse(res, {
|
|
1076
1078
|
cloud_enabled: !!cloud.enabled,
|
|
1077
1079
|
api_base: cloud.api_base || null,
|
|
@@ -1079,6 +1081,7 @@ export class AwarenessLocalDaemon {
|
|
|
1079
1081
|
auto_sync: cloud.auto_sync ?? true,
|
|
1080
1082
|
last_push_at: cloud.last_push_at || null,
|
|
1081
1083
|
last_pull_at: cloud.last_pull_at || null,
|
|
1084
|
+
history,
|
|
1082
1085
|
});
|
|
1083
1086
|
}
|
|
1084
1087
|
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-stdio.mjs — Lightweight stdio MCP proxy for Awareness Local.
|
|
3
|
+
*
|
|
4
|
+
* Registers the same 5 tools as the HTTP daemon (awareness_init,
|
|
5
|
+
* awareness_recall, awareness_record, awareness_lookup,
|
|
6
|
+
* awareness_get_agent_prompt) but proxies every call to the local daemon
|
|
7
|
+
* via HTTP JSON-RPC at http://localhost:{port}/mcp.
|
|
8
|
+
*
|
|
9
|
+
* If the daemon is not running it is auto-started before the first call.
|
|
10
|
+
*
|
|
11
|
+
* stdout is reserved for the stdio MCP protocol — all logging goes to stderr.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { spawn } from 'node:child_process';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import http from 'node:http';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Logging — always to stderr so stdout stays clean for stdio protocol
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
function log(...args) {
|
|
30
|
+
process.stderr.write(`[awareness-stdio] ${args.join(' ')}\n`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// HTTP helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Simple HTTP POST that returns parsed JSON.
|
|
39
|
+
* Uses only node:http to avoid external dependencies.
|
|
40
|
+
*/
|
|
41
|
+
function httpPost(url, body) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const u = new URL(url);
|
|
44
|
+
const data = JSON.stringify(body);
|
|
45
|
+
const req = http.request(
|
|
46
|
+
{
|
|
47
|
+
hostname: u.hostname,
|
|
48
|
+
port: u.port,
|
|
49
|
+
path: u.pathname,
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
'Content-Length': Buffer.byteLength(data),
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
(res) => {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
res.on('data', (c) => chunks.push(c));
|
|
59
|
+
res.on('end', () => {
|
|
60
|
+
try {
|
|
61
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
reject(new Error(`Failed to parse daemon response: ${e.message}`));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
);
|
|
68
|
+
req.on('error', reject);
|
|
69
|
+
req.write(data);
|
|
70
|
+
req.end();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Quick health check — resolves true if daemon responds, false otherwise.
|
|
76
|
+
*/
|
|
77
|
+
function checkHealth(port) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const req = http.get(`http://127.0.0.1:${port}/healthz`, (res) => {
|
|
80
|
+
// Any response means daemon is up
|
|
81
|
+
res.resume();
|
|
82
|
+
resolve(true);
|
|
83
|
+
});
|
|
84
|
+
req.on('error', () => resolve(false));
|
|
85
|
+
req.setTimeout(2000, () => {
|
|
86
|
+
req.destroy();
|
|
87
|
+
resolve(false);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Daemon lifecycle
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Ensure the daemon is running. If not, spawn it and poll /healthz for up
|
|
98
|
+
* to 15 seconds.
|
|
99
|
+
*/
|
|
100
|
+
async function ensureDaemon(port) {
|
|
101
|
+
if (await checkHealth(port)) return;
|
|
102
|
+
|
|
103
|
+
log('Daemon not reachable — starting...');
|
|
104
|
+
const binPath = join(__dirname, '..', 'bin', 'awareness-local.mjs');
|
|
105
|
+
const child = spawn(process.execPath, [binPath, 'start'], {
|
|
106
|
+
stdio: 'ignore',
|
|
107
|
+
detached: true,
|
|
108
|
+
env: { ...process.env, PORT: String(port) },
|
|
109
|
+
});
|
|
110
|
+
child.unref();
|
|
111
|
+
|
|
112
|
+
// Poll healthz for up to 15 seconds
|
|
113
|
+
const deadline = Date.now() + 15_000;
|
|
114
|
+
while (Date.now() < deadline) {
|
|
115
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
116
|
+
if (await checkHealth(port)) {
|
|
117
|
+
log('Daemon is ready.');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Daemon did not become healthy within 15s (port ${port}). ` +
|
|
123
|
+
`Try running "npx awareness-local start" manually.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// JSON-RPC proxy
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
let _daemonChecked = false;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Proxy a tool call to the daemon via JSON-RPC over HTTP.
|
|
135
|
+
*
|
|
136
|
+
* @param {number} port
|
|
137
|
+
* @param {string} toolName — MCP tool name (e.g. "awareness_init")
|
|
138
|
+
* @param {object} args — tool arguments
|
|
139
|
+
* @returns {object} parsed result from daemon
|
|
140
|
+
*/
|
|
141
|
+
async function proxyCall(port, toolName, args) {
|
|
142
|
+
// Lazy daemon startup — only check once per process
|
|
143
|
+
if (!_daemonChecked) {
|
|
144
|
+
await ensureDaemon(port);
|
|
145
|
+
_daemonChecked = true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const rpcBody = {
|
|
149
|
+
jsonrpc: '2.0',
|
|
150
|
+
id: 1,
|
|
151
|
+
method: 'tools/call',
|
|
152
|
+
params: { name: toolName, arguments: args },
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
let response;
|
|
156
|
+
try {
|
|
157
|
+
response = await httpPost(`http://127.0.0.1:${port}/mcp`, rpcBody);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// Daemon may have died — try to restart once
|
|
160
|
+
log(`Proxy error, retrying after daemon restart: ${err.message}`);
|
|
161
|
+
_daemonChecked = false;
|
|
162
|
+
await ensureDaemon(port);
|
|
163
|
+
_daemonChecked = true;
|
|
164
|
+
response = await httpPost(`http://127.0.0.1:${port}/mcp`, rpcBody);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// JSON-RPC error
|
|
168
|
+
if (response.error) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Daemon RPC error ${response.error.code}: ${response.error.message}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// The daemon wraps results as:
|
|
175
|
+
// result.content[0].text = JSON.stringify(payload)
|
|
176
|
+
const result = response.result;
|
|
177
|
+
if (result?.content?.[0]?.text) {
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(result.content[0].text);
|
|
180
|
+
} catch {
|
|
181
|
+
// If it's not parseable JSON, return as-is
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Tool registration helpers
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Wrap a proxy result in the standard MCP content envelope.
|
|
194
|
+
*/
|
|
195
|
+
function mcpResult(data) {
|
|
196
|
+
return { content: [{ type: 'text', text: JSON.stringify(data) }] };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function mcpError(message) {
|
|
200
|
+
return {
|
|
201
|
+
content: [{ type: 'text', text: JSON.stringify({ error: message }) }],
|
|
202
|
+
isError: true,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Register tools — schemas match mcp-server.mjs exactly
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
function registerTools(server, port) {
|
|
211
|
+
// ======================== awareness_init ==================================
|
|
212
|
+
|
|
213
|
+
server.tool(
|
|
214
|
+
'awareness_init',
|
|
215
|
+
{
|
|
216
|
+
memory_id: z.string().optional().describe(
|
|
217
|
+
'Memory identifier (ignored in local mode, uses project dir)',
|
|
218
|
+
),
|
|
219
|
+
source: z.string().optional().describe('Client source identifier'),
|
|
220
|
+
days: z.number().optional().default(7).describe(
|
|
221
|
+
'Days of history to load',
|
|
222
|
+
),
|
|
223
|
+
max_cards: z.number().optional().default(5),
|
|
224
|
+
max_tasks: z.number().optional().default(5),
|
|
225
|
+
},
|
|
226
|
+
async (params) => {
|
|
227
|
+
try {
|
|
228
|
+
return mcpResult(await proxyCall(port, 'awareness_init', params));
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return mcpError(`awareness_init failed: ${err.message}`);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// ======================== awareness_recall ================================
|
|
236
|
+
|
|
237
|
+
server.tool(
|
|
238
|
+
'awareness_recall',
|
|
239
|
+
{
|
|
240
|
+
semantic_query: z.string().optional().default('').describe(
|
|
241
|
+
'Natural language search query (required for search)',
|
|
242
|
+
),
|
|
243
|
+
keyword_query: z.string().optional().default('').describe(
|
|
244
|
+
'Exact keyword match for BM25 full-text search',
|
|
245
|
+
),
|
|
246
|
+
scope: z.enum(['all', 'timeline', 'knowledge', 'insights'])
|
|
247
|
+
.optional().default('all')
|
|
248
|
+
.describe('Search scope'),
|
|
249
|
+
recall_mode: z.enum(['precise', 'session', 'structured', 'hybrid', 'auto'])
|
|
250
|
+
.optional().default('hybrid')
|
|
251
|
+
.describe('Search mode (hybrid recommended)'),
|
|
252
|
+
limit: z.number().min(1).max(30).optional().default(10)
|
|
253
|
+
.describe('Max results'),
|
|
254
|
+
detail: z.enum(['summary', 'full']).optional().default('summary')
|
|
255
|
+
.describe(
|
|
256
|
+
'summary = lightweight index (~50-100 tokens each); ' +
|
|
257
|
+
'full = complete content for specified ids',
|
|
258
|
+
),
|
|
259
|
+
ids: z.array(z.string()).optional().describe(
|
|
260
|
+
'Item IDs to expand when detail=full (from a prior detail=summary call)',
|
|
261
|
+
),
|
|
262
|
+
agent_role: z.string().optional().default('').describe('Agent role filter'),
|
|
263
|
+
},
|
|
264
|
+
async (params) => {
|
|
265
|
+
try {
|
|
266
|
+
return mcpResult(await proxyCall(port, 'awareness_recall', params));
|
|
267
|
+
} catch (err) {
|
|
268
|
+
return mcpError(`awareness_recall failed: ${err.message}`);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// ======================== awareness_record ================================
|
|
274
|
+
|
|
275
|
+
server.tool(
|
|
276
|
+
'awareness_record',
|
|
277
|
+
{
|
|
278
|
+
action: z.enum([
|
|
279
|
+
'remember', 'remember_batch', 'update_task', 'submit_insights',
|
|
280
|
+
]).describe('Record action type'),
|
|
281
|
+
content: z.string().optional().describe('Memory content (markdown)'),
|
|
282
|
+
title: z.string().optional().describe('Memory title'),
|
|
283
|
+
items: z.array(z.object({
|
|
284
|
+
content: z.string(),
|
|
285
|
+
title: z.string().optional(),
|
|
286
|
+
event_type: z.string().optional(),
|
|
287
|
+
tags: z.array(z.string()).optional(),
|
|
288
|
+
insights: z.any().optional(),
|
|
289
|
+
})).optional().describe('Batch items for remember_batch'),
|
|
290
|
+
insights: z.object({
|
|
291
|
+
knowledge_cards: z.array(z.any()).optional(),
|
|
292
|
+
action_items: z.array(z.any()).optional(),
|
|
293
|
+
risks: z.array(z.any()).optional(),
|
|
294
|
+
}).optional().describe('Pre-extracted knowledge cards, tasks, risks'),
|
|
295
|
+
session_id: z.string().optional(),
|
|
296
|
+
agent_role: z.string().optional(),
|
|
297
|
+
event_type: z.string().optional(),
|
|
298
|
+
tags: z.array(z.string()).optional(),
|
|
299
|
+
// Task update fields
|
|
300
|
+
task_id: z.string().optional(),
|
|
301
|
+
status: z.string().optional(),
|
|
302
|
+
},
|
|
303
|
+
async (params) => {
|
|
304
|
+
try {
|
|
305
|
+
return mcpResult(await proxyCall(port, 'awareness_record', params));
|
|
306
|
+
} catch (err) {
|
|
307
|
+
return mcpError(`awareness_record failed: ${err.message}`);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// ======================== awareness_lookup ================================
|
|
313
|
+
|
|
314
|
+
server.tool(
|
|
315
|
+
'awareness_lookup',
|
|
316
|
+
{
|
|
317
|
+
type: z.enum([
|
|
318
|
+
'context', 'tasks', 'knowledge', 'risks',
|
|
319
|
+
'session_history', 'timeline',
|
|
320
|
+
]).describe(
|
|
321
|
+
'Data type to look up. ' +
|
|
322
|
+
'context = full dump, tasks = open tasks, knowledge = cards, ' +
|
|
323
|
+
'risks = risk items, session_history = past sessions, timeline = events',
|
|
324
|
+
),
|
|
325
|
+
limit: z.number().optional().default(10).describe('Max items'),
|
|
326
|
+
status: z.string().optional().describe('Status filter'),
|
|
327
|
+
category: z.string().optional().describe('Category filter (knowledge cards)'),
|
|
328
|
+
priority: z.string().optional().describe('Priority filter (tasks/risks)'),
|
|
329
|
+
session_id: z.string().optional().describe('Session ID (for session_history)'),
|
|
330
|
+
agent_role: z.string().optional().describe('Agent role filter'),
|
|
331
|
+
query: z.string().optional().describe('Keyword filter'),
|
|
332
|
+
},
|
|
333
|
+
async (params) => {
|
|
334
|
+
try {
|
|
335
|
+
return mcpResult(await proxyCall(port, 'awareness_lookup', params));
|
|
336
|
+
} catch (err) {
|
|
337
|
+
return mcpError(`awareness_lookup failed: ${err.message}`);
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// ======================== awareness_get_agent_prompt ======================
|
|
343
|
+
|
|
344
|
+
server.tool(
|
|
345
|
+
'awareness_get_agent_prompt',
|
|
346
|
+
{
|
|
347
|
+
role: z.string().optional().describe('Agent role to get prompt for'),
|
|
348
|
+
},
|
|
349
|
+
async (params) => {
|
|
350
|
+
try {
|
|
351
|
+
return mcpResult(await proxyCall(port, 'awareness_get_agent_prompt', params));
|
|
352
|
+
} catch (err) {
|
|
353
|
+
return mcpError(`awareness_get_agent_prompt failed: ${err.message}`);
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// Public API
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Start the stdio MCP proxy server.
|
|
365
|
+
*
|
|
366
|
+
* @param {object} opts
|
|
367
|
+
* @param {number} [opts.port=37800] — daemon HTTP port to proxy to
|
|
368
|
+
* @param {string} [opts.projectDir] — project directory (unused in proxy,
|
|
369
|
+
* but accepted for API symmetry with direct-mode startup)
|
|
370
|
+
*/
|
|
371
|
+
export async function startStdioMcp({ port = 37800, projectDir } = {}) {
|
|
372
|
+
log(`Starting stdio MCP proxy (daemon port=${port})`);
|
|
373
|
+
|
|
374
|
+
const server = new McpServer({
|
|
375
|
+
name: 'awareness-local-stdio',
|
|
376
|
+
version: '1.0.0',
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
registerTools(server, port);
|
|
380
|
+
|
|
381
|
+
const transport = new StdioServerTransport();
|
|
382
|
+
await server.connect(transport);
|
|
383
|
+
|
|
384
|
+
log('stdio MCP proxy connected and ready.');
|
|
385
|
+
return server;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
// CLI entry — run directly with `node src/mcp-stdio.mjs`
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
393
|
+
const port = parseInt(process.env.AWARENESS_PORT || process.env.PORT || '37800', 10);
|
|
394
|
+
startStdioMcp({ port }).catch((err) => {
|
|
395
|
+
process.stderr.write(`[awareness-stdio] Fatal: ${err.message}\n`);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
});
|
|
398
|
+
}
|
package/src/web/index.html
CHANGED
|
@@ -215,6 +215,17 @@ body {
|
|
|
215
215
|
.sync-history { margin-top: 20px; }
|
|
216
216
|
.sync-history h4 { font-size: 0.9rem; font-weight: 600; margin-bottom: 12px; color: var(--text-secondary); }
|
|
217
217
|
.sync-history-empty { font-size: 0.85rem; color: var(--text-muted); padding: 16px; text-align: center; }
|
|
218
|
+
.sync-history-list { max-height: 300px; overflow-y: auto; }
|
|
219
|
+
.sync-history-item {
|
|
220
|
+
display: flex; align-items: center; gap: 10px;
|
|
221
|
+
padding: 8px 12px; border-bottom: 1px solid var(--border);
|
|
222
|
+
font-size: 0.85rem;
|
|
223
|
+
}
|
|
224
|
+
.sync-history-item:last-child { border-bottom: none; }
|
|
225
|
+
.sync-history-icon { font-size: 1rem; flex-shrink: 0; }
|
|
226
|
+
.sync-history-type { font-weight: 500; min-width: 70px; }
|
|
227
|
+
.sync-history-count { color: var(--text-secondary); }
|
|
228
|
+
.sync-history-time { margin-left: auto; color: var(--text-muted); font-size: 0.8rem; white-space: nowrap; }
|
|
218
229
|
|
|
219
230
|
/* ---- Settings ---- */
|
|
220
231
|
.settings-section { margin-bottom: 28px; }
|
|
@@ -754,6 +765,31 @@ async function loadSync() {
|
|
|
754
765
|
descEl.textContent = 'Memories are stored locally only. Connect to cloud for backup and cross-device sync.';
|
|
755
766
|
btnEl.textContent = 'Connect to Cloud';
|
|
756
767
|
}
|
|
768
|
+
|
|
769
|
+
// Render sync history
|
|
770
|
+
var historyEl = document.getElementById('sync-history');
|
|
771
|
+
if (data.history && data.history.length > 0) {
|
|
772
|
+
var html = '<h4>Sync History</h4><div class="sync-history-list">';
|
|
773
|
+
for (var i = 0; i < data.history.length; i++) {
|
|
774
|
+
var item = data.history[i];
|
|
775
|
+
var dir = (item.details && item.details.direction) || 'push';
|
|
776
|
+
var icon = dir === 'pull' ? '\u2B07\uFE0F' : '\u2B06\uFE0F';
|
|
777
|
+
var type = item.type || 'memories';
|
|
778
|
+
var count = (item.details && item.details.count) || 0;
|
|
779
|
+
var time = item.timestamp ? formatDate(item.timestamp) : '';
|
|
780
|
+
html += '<div class="sync-history-item">'
|
|
781
|
+
+ '<span class="sync-history-icon">' + icon + '</span>'
|
|
782
|
+
+ '<span class="sync-history-type">' + type + '</span>'
|
|
783
|
+
+ '<span class="sync-history-count">' + count + ' ' + dir + '</span>'
|
|
784
|
+
+ '<span class="sync-history-time">' + time + '</span>'
|
|
785
|
+
+ '</div>';
|
|
786
|
+
}
|
|
787
|
+
html += '</div>';
|
|
788
|
+
historyEl.innerHTML = html;
|
|
789
|
+
} else {
|
|
790
|
+
historyEl.innerHTML = '<h4>Sync History</h4>'
|
|
791
|
+
+ '<div class="sync-history-empty">No sync history yet. Connect to cloud to enable sync.</div>';
|
|
792
|
+
}
|
|
757
793
|
}
|
|
758
794
|
|
|
759
795
|
var _authApiKey = null;
|