wendkeep 0.69.0 → 0.71.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.
@@ -0,0 +1,146 @@
1
+ import { homedir } from 'node:os';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { appendObserverEvent, readObserverIndex, registerObserverProject } from './observer-store.mjs';
4
+ import { buildProjectSnapshot } from './observer-snapshot.mjs';
5
+ import { compareMemoryParity, publishObserverMemory } from './observer-memory-publish.mjs';
6
+ import { startObserverServer } from './observer-server.mjs';
7
+ import { resolveProjectVault } from '../packages/vault/src/project-vault.mjs';
8
+
9
+ export const OBSERVER_HELP = `wendkeep observer — Observer local multi-projeto
10
+
11
+ Uso:
12
+ wendkeep observer serve [--data-dir P] [--host 127.0.0.1] [--port 8787]
13
+ [--allow-non-loopback]
14
+ wendkeep observer register --project P [--vault V] [--data-dir D] [--json]
15
+ wendkeep observer publish --project P [--vault V] [--data-dir D] [--json]
16
+ wendkeep observer memory import --project P [--vault V] [--url U] [--json]
17
+ wendkeep observer status [--data-dir D] [--json]
18
+
19
+ O Observer local pode manter snapshots operacionais e uma cópia completa da memória em volume
20
+ Docker. O comando memory import faz a primeira migração de um vault para o container.
21
+ `;
22
+
23
+ function optionValue(argv, name) {
24
+ const index = argv.indexOf(name);
25
+ if (index >= 0) return argv[index + 1] || '';
26
+ return argv.find((item) => item.startsWith(`${name}=`))?.slice(name.length + 1) || '';
27
+ }
28
+
29
+ function dataDir(argv) {
30
+ return resolve(optionValue(argv, '--data-dir')
31
+ || process.env.WENDKEEP_OBSERVER_DATA_DIR
32
+ || `${homedir()}/.wendkeep-observer`);
33
+ }
34
+
35
+ function projectRoot(argv) {
36
+ const value = optionValue(argv, '--project') || process.cwd();
37
+ return isAbsolute(value) ? resolve(value) : resolve(process.cwd(), value);
38
+ }
39
+
40
+ function vaultBase(argv, root) {
41
+ const explicit = optionValue(argv, '--vault');
42
+ if (explicit) return isAbsolute(explicit) ? resolve(explicit) : resolve(root, explicit);
43
+ return resolveProjectVault({ startDir: root }).base;
44
+ }
45
+
46
+ function print(value, asJson, write = (chunk) => process.stdout.write(chunk)) {
47
+ write((asJson ? JSON.stringify(value, null, 2) : String(value)) + '\n');
48
+ }
49
+
50
+ function summary(index) {
51
+ return {
52
+ schema_version: index.schema_version,
53
+ projects: index.projects.map(({ snapshot, ...item }) => item),
54
+ };
55
+ }
56
+
57
+ export async function runObserver(argv = [], { write = (chunk) => process.stdout.write(chunk) } = {}) {
58
+ const [sub] = argv;
59
+ const asJson = argv.includes('--json');
60
+ if (!sub || sub === 'help') {
61
+ process.stdout.write(OBSERVER_HELP);
62
+ return 0;
63
+ }
64
+ const dir = dataDir(argv);
65
+
66
+ if (sub === 'status') {
67
+ print(summary(readObserverIndex(dir)), asJson, write);
68
+ return 0;
69
+ }
70
+
71
+ if (sub === 'serve') {
72
+ const host = optionValue(argv, '--host') || '127.0.0.1';
73
+ const server = await startObserverServer({
74
+ dataDir: dir,
75
+ host,
76
+ port: Number(optionValue(argv, '--port') || 8787),
77
+ allowNonLoopback: argv.includes('--allow-non-loopback'),
78
+ });
79
+ const address = server.address();
80
+ process.stdout.write(`wendkeep observer listening: http://${address.address}:${address.port}\n`);
81
+ return 0;
82
+ }
83
+
84
+ if (sub === 'memory') {
85
+ const action = argv[1] || '';
86
+ if (action !== 'import') throw new Error('observer memory: use memory import.');
87
+ const root = projectRoot(argv);
88
+ const vault = vaultBase(argv, root);
89
+ const snapshot = buildProjectSnapshot({ vaultBase: vault, projectRoot: root });
90
+ const url = optionValue(argv, '--url') || process.env.WENDKEEP_OBSERVER_URL || '';
91
+ if (!url) throw new Error('observer memory import: --url ou WENDKEEP_OBSERVER_URL é obrigatório.');
92
+ const headers = { 'content-type': 'application/json', accept: 'application/json' };
93
+ const registration = await fetch(
94
+ String(url).replace(/\/$/, '') + '/v1/projects/' + encodeURIComponent(snapshot.project_id),
95
+ {
96
+ method: 'PUT',
97
+ headers,
98
+ body: JSON.stringify({
99
+ project_id: snapshot.project_id,
100
+ project_name: snapshot.project_name,
101
+ wendkeep_version: snapshot.wendkeep_version,
102
+ }),
103
+ },
104
+ );
105
+ if (!registration.ok) throw new Error('Observer não registrou o projeto: HTTP ' + registration.status + '.');
106
+ const memory = await publishObserverMemory({
107
+ vaultBase: vault,
108
+ projectId: snapshot.project_id,
109
+ url,
110
+ });
111
+ const parity = await compareMemoryParity({
112
+ vaultBase: vault,
113
+ projectId: snapshot.project_id,
114
+ url,
115
+ });
116
+ const result = {
117
+ ok: memory.ok && parity.missing === 0 && parity.mismatched === 0,
118
+ project_id: snapshot.project_id,
119
+ memory,
120
+ parity,
121
+ };
122
+ print(result, asJson, write);
123
+ return result.ok ? 0 : 1;
124
+ }
125
+
126
+ if (!['register', 'publish'].includes(sub)) throw new Error('observer: subcomando desconhecido: ' + sub);
127
+ const root = projectRoot(argv);
128
+ const vault = vaultBase(argv, root);
129
+ const snapshot = buildProjectSnapshot({ vaultBase: vault, projectRoot: root });
130
+
131
+ if (sub === 'register') {
132
+ const result = registerObserverProject(dir, {
133
+ projectId: snapshot.project_id,
134
+ projectName: snapshot.project_name,
135
+ wendkeepVersion: snapshot.wendkeep_version,
136
+ });
137
+ if (!result.registered) throw new Error(result.errors.join(' '));
138
+ print(result, asJson, write);
139
+ return 0;
140
+ }
141
+
142
+ const result = appendObserverEvent(dir, snapshot);
143
+ if (!result.accepted && !result.duplicate) throw new Error(result.errors.join(' '));
144
+ print({ ok: true, ...result }, asJson, write);
145
+ return 0;
146
+ }
package/src/taxonomy.mjs CHANGED
@@ -99,6 +99,7 @@ export const HOOK_FILES = [
99
99
  'change-guard.mjs',
100
100
  'change-nag.mjs',
101
101
  'plan-capture.mjs',
102
+ 'observer-publish.mjs',
102
103
  ];
103
104
 
104
105
  // Hook scripts that are safe to invoke directly via `wendkeep hook <name>`.
@@ -122,6 +123,7 @@ export const RUNNABLE_HOOKS = [
122
123
  'change-guard',
123
124
  'change-nag',
124
125
  'plan-capture',
126
+ 'observer-publish',
125
127
  ];
126
128
 
127
129
  // --- companion plugins / MCP --------------------------------------------------