total-recall-brain 3.13.0 → 3.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +1 -0
- package/frontend/src/App.tsx +109 -152
- package/frontend/src/api.ts +605 -23
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +3 -3
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +5 -5
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +412 -323
- package/frontend/src/pages/SettingsPage.tsx +825 -922
- package/frontend/src/pages/SkillsPage.tsx +272 -147
- package/frontend/src/pages/UsagePage.tsx +23 -31
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +13 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +21 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +26 -16
- package/src/server/auth.mjs +51 -0
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +198 -25
- package/src/server/route-manifest.json +156 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
|
@@ -71,10 +71,34 @@
|
|
|
71
71
|
"method": "POST",
|
|
72
72
|
"path": "/api/context/stream"
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
"method": "GET",
|
|
76
|
+
"path": "/api/dashboard/instructions"
|
|
77
|
+
},
|
|
74
78
|
{
|
|
75
79
|
"method": "POST",
|
|
76
80
|
"path": "/api/diagnostics/agents"
|
|
77
81
|
},
|
|
82
|
+
{
|
|
83
|
+
"method": "DELETE",
|
|
84
|
+
"path": "/api/docs"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"method": "GET",
|
|
88
|
+
"path": "/api/docs"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"method": "POST",
|
|
92
|
+
"path": "/api/docs"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"method": "PUT",
|
|
96
|
+
"path": "/api/docs"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"method": "GET",
|
|
100
|
+
"path": "/api/docs/read"
|
|
101
|
+
},
|
|
78
102
|
{
|
|
79
103
|
"method": "POST",
|
|
80
104
|
"path": "/api/dream"
|
|
@@ -231,6 +255,74 @@
|
|
|
231
255
|
"method": "POST",
|
|
232
256
|
"path": "/api/scripts/:name/run"
|
|
233
257
|
},
|
|
258
|
+
{
|
|
259
|
+
"method": "GET",
|
|
260
|
+
"path": "/api/secrets"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"method": "POST",
|
|
264
|
+
"path": "/api/secrets"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"method": "DELETE",
|
|
268
|
+
"path": "/api/secrets/:key"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"method": "PATCH",
|
|
272
|
+
"path": "/api/secrets/:key"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"method": "POST",
|
|
276
|
+
"path": "/api/secrets/:key/reveal"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"method": "POST",
|
|
280
|
+
"path": "/api/secrets/:key/rotate"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"method": "POST",
|
|
284
|
+
"path": "/api/secrets/deploy-payload"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"method": "GET",
|
|
288
|
+
"path": "/api/secrets/export-env"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"method": "POST",
|
|
292
|
+
"path": "/api/secrets/export-env"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"method": "POST",
|
|
296
|
+
"path": "/api/secrets/import-env"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"method": "POST",
|
|
300
|
+
"path": "/api/secrets/parse-env"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"method": "GET",
|
|
304
|
+
"path": "/api/secrets/providers"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"method": "GET",
|
|
308
|
+
"path": "/api/secrets/rotation-due"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"method": "POST",
|
|
312
|
+
"path": "/api/secrets/rotation-due/enqueue"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"method": "GET",
|
|
316
|
+
"path": "/api/secrets/scan-env"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"method": "GET",
|
|
320
|
+
"path": "/api/secrets/usage"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"method": "POST",
|
|
324
|
+
"path": "/api/secrets/usage"
|
|
325
|
+
},
|
|
234
326
|
{
|
|
235
327
|
"method": "GET",
|
|
236
328
|
"path": "/api/sessions"
|
|
@@ -267,6 +359,10 @@
|
|
|
267
359
|
"method": "PUT",
|
|
268
360
|
"path": "/api/skills/:name"
|
|
269
361
|
},
|
|
362
|
+
{
|
|
363
|
+
"method": "GET",
|
|
364
|
+
"path": "/api/skills/:name/files"
|
|
365
|
+
},
|
|
270
366
|
{
|
|
271
367
|
"method": "POST",
|
|
272
368
|
"path": "/api/skills/install"
|
|
@@ -299,6 +395,18 @@
|
|
|
299
395
|
"method": "GET",
|
|
300
396
|
"path": "/api/ssss/spec"
|
|
301
397
|
},
|
|
398
|
+
{
|
|
399
|
+
"method": "POST",
|
|
400
|
+
"path": "/api/sync/remote-vault/proposals/:id/decision"
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"method": "POST",
|
|
404
|
+
"path": "/api/sync/remote-vault/run"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"method": "GET",
|
|
408
|
+
"path": "/api/sync/remote-vault/status"
|
|
409
|
+
},
|
|
302
410
|
{
|
|
303
411
|
"method": "GET",
|
|
304
412
|
"path": "/api/tasks"
|
|
@@ -307,10 +415,18 @@
|
|
|
307
415
|
"method": "POST",
|
|
308
416
|
"path": "/api/tasks"
|
|
309
417
|
},
|
|
418
|
+
{
|
|
419
|
+
"method": "POST",
|
|
420
|
+
"path": "/api/tasks/:id/retry"
|
|
421
|
+
},
|
|
310
422
|
{
|
|
311
423
|
"method": "DELETE",
|
|
312
424
|
"path": "/api/tasks/cleanup"
|
|
313
425
|
},
|
|
426
|
+
{
|
|
427
|
+
"method": "GET",
|
|
428
|
+
"path": "/api/tasks/failed"
|
|
429
|
+
},
|
|
314
430
|
{
|
|
315
431
|
"method": "POST",
|
|
316
432
|
"path": "/api/tts"
|
|
@@ -347,6 +463,46 @@
|
|
|
347
463
|
"method": "GET",
|
|
348
464
|
"path": "/api/vault/status"
|
|
349
465
|
},
|
|
466
|
+
{
|
|
467
|
+
"method": "GET",
|
|
468
|
+
"path": "/api/views"
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"method": "POST",
|
|
472
|
+
"path": "/api/views"
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"method": "DELETE",
|
|
476
|
+
"path": "/api/views/:id"
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"method": "POST",
|
|
480
|
+
"path": "/api/webauthn/assert/options"
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
"method": "POST",
|
|
484
|
+
"path": "/api/webauthn/assert/verify"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
"method": "DELETE",
|
|
488
|
+
"path": "/api/webauthn/credentials/:id"
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
"method": "POST",
|
|
492
|
+
"path": "/api/webauthn/register/options"
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"method": "POST",
|
|
496
|
+
"path": "/api/webauthn/register/verify"
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"method": "GET",
|
|
500
|
+
"path": "/api/webauthn/status"
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"method": "POST",
|
|
504
|
+
"path": "/api/webauthn/step-up/password"
|
|
505
|
+
},
|
|
350
506
|
{
|
|
351
507
|
"method": "POST",
|
|
352
508
|
"path": "/auth/change-password"
|
|
@@ -40,7 +40,15 @@ export const MODEL_CATALOG_DIR = path.join(ROOT, 'models', 'catalog', 'total-rec
|
|
|
40
40
|
* @returns {string} Absolute path to the memory-vault directory
|
|
41
41
|
*/
|
|
42
42
|
export function resolveVaultFromQuery(req) {
|
|
43
|
-
const
|
|
43
|
+
const rawBrainId = req.query?.brain || req.body?.brainId || req.headers?.['x-total-recall-brain'];
|
|
44
|
+
let brainId = rawBrainId;
|
|
45
|
+
|
|
46
|
+
if (rawBrainId && rawBrainId.includes(',')) {
|
|
47
|
+
const parts = rawBrainId.split(',').map(s => s.trim()).filter(Boolean);
|
|
48
|
+
if (parts.length > 0) {
|
|
49
|
+
brainId = parts[parts.length - 1];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
44
52
|
|
|
45
53
|
// No brain specified or explicitly global → default vault
|
|
46
54
|
if (!brainId || brainId === 'global') {
|
|
@@ -70,10 +78,41 @@ export function resolveVaultFromQuery(req) {
|
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
|
|
81
|
+
// tenant:<name> → ~/.agent/tenants/<name>/vault (any tenant the user configures)
|
|
82
|
+
if (brainId.startsWith('tenant:')) {
|
|
83
|
+
const tenantName = brainId.slice('tenant:'.length).replace(/[^a-zA-Z0-9._-]/g, '');
|
|
84
|
+
if (tenantName) {
|
|
85
|
+
const tenantVaultDir = path.join(os.homedir(), '.agent', 'tenants', tenantName, 'vault');
|
|
86
|
+
if (fs.existsSync(tenantVaultDir)) {
|
|
87
|
+
return tenantVaultDir;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
// Unknown brain ID format or resolution failed → default vault
|
|
74
93
|
return VAULT_DIR;
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
export function resolveAllVaultsFromQuery(req) {
|
|
97
|
+
const rawBrainId = req.query?.brain || req.body?.brainId || req.headers?.['x-total-recall-brain'];
|
|
98
|
+
if (!rawBrainId) return [VAULT_DIR];
|
|
99
|
+
|
|
100
|
+
const ids = rawBrainId.split(',').map(s => s.trim()).filter(Boolean);
|
|
101
|
+
const vaults = [];
|
|
102
|
+
|
|
103
|
+
for (const id of ids) {
|
|
104
|
+
// We can reuse resolveVaultFromQuery by mocking the req object
|
|
105
|
+
const vault = resolveVaultFromQuery({ query: { brain: id } });
|
|
106
|
+
if (vault) {
|
|
107
|
+
vaults.push(vault);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Deduplicate vaults
|
|
112
|
+
const uniqueVaults = [...new Set(vaults)];
|
|
113
|
+
return uniqueVaults.length > 0 ? uniqueVaults : [VAULT_DIR];
|
|
114
|
+
}
|
|
115
|
+
|
|
77
116
|
export function notFound(res, msg) {
|
|
78
117
|
return res.status(404).json({ error: msg || 'Not found' });
|
|
79
118
|
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import matter from 'gray-matter';
|
|
7
|
+
|
|
8
|
+
import { requireAuth, requireScope } from '../auth.mjs';
|
|
9
|
+
import {
|
|
10
|
+
badRequest,
|
|
11
|
+
notFound,
|
|
12
|
+
serverError,
|
|
13
|
+
resolveVaultFromQuery,
|
|
14
|
+
CONFIG_DIR
|
|
15
|
+
} from './_shared.mjs';
|
|
16
|
+
import { getNodes, invalidate } from '../../core/vault-cache.mjs';
|
|
17
|
+
import { processOperationAsync } from '../../core/operation-validator.mjs';
|
|
18
|
+
|
|
19
|
+
const router = express.Router();
|
|
20
|
+
|
|
21
|
+
function getRegistryTypes(brainDir) {
|
|
22
|
+
const globalRegistryPath = path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'config', 'project-registry.json');
|
|
23
|
+
// Portability is resolved per-type from the registry (core + extensions)
|
|
24
|
+
// Since we just need portability mapping for types, let's load it dynamically.
|
|
25
|
+
// Actually, `@ssss/cli` parses things.
|
|
26
|
+
// For now, let's just return a generic helper.
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Ensure the path is safe
|
|
31
|
+
function isSafePath(p) {
|
|
32
|
+
const normalized = path.normalize(p).replace(/^(\.\.(\/|\\|$))+/, '');
|
|
33
|
+
return normalized === p && !p.startsWith('/') && !p.includes('..');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* GET /api/docs
|
|
38
|
+
* List documents with filtering and pagination.
|
|
39
|
+
*/
|
|
40
|
+
router.get('/api/docs', requireAuth, requireScope('ssss:read'), (req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
43
|
+
if (!fs.existsSync(vaultDir)) {
|
|
44
|
+
return res.json({ total: 0, docs: [] });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let nodes = getNodes(vaultDir);
|
|
48
|
+
|
|
49
|
+
const { type, portability, status, tag, xKind, q, limit, offset } = req.query;
|
|
50
|
+
|
|
51
|
+
if (type) nodes = nodes.filter(n => n.type === type);
|
|
52
|
+
if (status) nodes = nodes.filter(n => n.status === status);
|
|
53
|
+
if (xKind) nodes = nodes.filter(n => n.x_kind === xKind);
|
|
54
|
+
if (tag) nodes = nodes.filter(n => n.tags && n.tags.includes(tag));
|
|
55
|
+
|
|
56
|
+
// Naive resolution for portability if requested
|
|
57
|
+
// (Assuming vault-cache nodes already have a 'portability' property or we extract it)
|
|
58
|
+
if (portability) {
|
|
59
|
+
nodes = nodes.filter(n => {
|
|
60
|
+
// Some frontmatter might define x_portability directly
|
|
61
|
+
if (n.x_portability) return n.x_portability === portability;
|
|
62
|
+
// Hardcoded defaults per SSSS core:
|
|
63
|
+
if (['memory', 'rule', 'workflow', 'assistant', 'page', 'theme'].includes(n.type)) {
|
|
64
|
+
return 'structural' === portability;
|
|
65
|
+
}
|
|
66
|
+
return 'tenant_private' === portability;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (q) {
|
|
71
|
+
const qs = q.toLowerCase();
|
|
72
|
+
nodes = nodes.filter(n =>
|
|
73
|
+
(n.title && n.title.toLowerCase().includes(qs)) ||
|
|
74
|
+
(n.slug && n.slug.toLowerCase().includes(qs))
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const total = nodes.length;
|
|
79
|
+
|
|
80
|
+
// Sort by updated descending
|
|
81
|
+
nodes.sort((a, b) => new Date(b.updated || 0) - new Date(a.updated || 0));
|
|
82
|
+
|
|
83
|
+
const skip = parseInt(offset, 10) || 0;
|
|
84
|
+
const take = parseInt(limit, 10) || 50;
|
|
85
|
+
const page = nodes.slice(skip, skip + take);
|
|
86
|
+
|
|
87
|
+
res.json({
|
|
88
|
+
total,
|
|
89
|
+
docs: page.map(n => ({
|
|
90
|
+
path: n._filePath ? path.relative(vaultDir, n._filePath) : `${n.category || 'uncategorized'}/${n.slug}.md`,
|
|
91
|
+
type: n.type || 'memory',
|
|
92
|
+
portability: n.x_portability || (['memory', 'rule', 'workflow', 'assistant', 'page', 'theme'].includes(n.type) ? 'structural' : 'tenant_private'),
|
|
93
|
+
name: n.title || n.slug,
|
|
94
|
+
status: n.status,
|
|
95
|
+
tags: n.tags || [],
|
|
96
|
+
updatedAt: n.updated,
|
|
97
|
+
frontmatter: { ...n, body: undefined, _filePath: undefined }
|
|
98
|
+
}))
|
|
99
|
+
});
|
|
100
|
+
} catch (err) { serverError(res, err); }
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* GET /api/docs/read
|
|
105
|
+
* Read a single document by path
|
|
106
|
+
*/
|
|
107
|
+
router.get('/api/docs/read', requireAuth, requireScope('ssss:read'), (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
110
|
+
const relPath = req.query.path;
|
|
111
|
+
if (!relPath || !isSafePath(relPath)) return badRequest(res, 'Invalid path');
|
|
112
|
+
|
|
113
|
+
const absPath = path.join(vaultDir, relPath);
|
|
114
|
+
if (!fs.existsSync(absPath)) return notFound(res, 'Document not found');
|
|
115
|
+
|
|
116
|
+
const raw = fs.readFileSync(absPath, 'utf8');
|
|
117
|
+
const parsed = matter(raw);
|
|
118
|
+
|
|
119
|
+
res.json({
|
|
120
|
+
path: relPath,
|
|
121
|
+
raw,
|
|
122
|
+
frontmatter: parsed.data,
|
|
123
|
+
body: parsed.content
|
|
124
|
+
});
|
|
125
|
+
} catch (err) { serverError(res, err); }
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* POST /api/docs
|
|
130
|
+
* Create a new document
|
|
131
|
+
*/
|
|
132
|
+
router.post('/api/docs', requireAuth, requireScope('ssss:write'), async (req, res) => {
|
|
133
|
+
try {
|
|
134
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
135
|
+
const { path: relPath, content } = req.body;
|
|
136
|
+
|
|
137
|
+
if (!relPath || !isSafePath(relPath)) return badRequest(res, 'Invalid path');
|
|
138
|
+
const absPath = path.join(vaultDir, relPath);
|
|
139
|
+
if (fs.existsSync(absPath)) return res.status(409).json({ error: 'Document already exists' });
|
|
140
|
+
|
|
141
|
+
const envelope = {
|
|
142
|
+
type: 'operation',
|
|
143
|
+
idempotency_key: crypto.randomUUID(),
|
|
144
|
+
path: relPath,
|
|
145
|
+
workspace_id: 'default',
|
|
146
|
+
content
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const result = await processOperationAsync(envelope, vaultDir, { agentRole: 'admin' });
|
|
150
|
+
if (!result.success) {
|
|
151
|
+
return res.status(400).json({ error: 'Validation failed', details: result });
|
|
152
|
+
}
|
|
153
|
+
invalidate(vaultDir);
|
|
154
|
+
res.json({ success: true, result });
|
|
155
|
+
} catch (err) { serverError(res, err); }
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* PUT /api/docs
|
|
160
|
+
* Update an existing document
|
|
161
|
+
*/
|
|
162
|
+
router.put('/api/docs', requireAuth, requireScope('ssss:write'), async (req, res) => {
|
|
163
|
+
try {
|
|
164
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
165
|
+
const { path: relPath, content } = req.body;
|
|
166
|
+
|
|
167
|
+
if (!relPath || !isSafePath(relPath)) return badRequest(res, 'Invalid path');
|
|
168
|
+
const absPath = path.join(vaultDir, relPath);
|
|
169
|
+
if (!fs.existsSync(absPath)) return notFound(res, 'Document not found');
|
|
170
|
+
|
|
171
|
+
const envelope = {
|
|
172
|
+
type: 'operation',
|
|
173
|
+
idempotency_key: crypto.randomUUID(),
|
|
174
|
+
path: relPath,
|
|
175
|
+
workspace_id: 'default',
|
|
176
|
+
content
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const result = await processOperationAsync(envelope, vaultDir, { agentRole: 'admin' });
|
|
180
|
+
if (!result.success) {
|
|
181
|
+
return res.status(400).json({ error: 'Validation failed', details: result });
|
|
182
|
+
}
|
|
183
|
+
invalidate(vaultDir);
|
|
184
|
+
res.json({ success: true, result });
|
|
185
|
+
} catch (err) { serverError(res, err); }
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* DELETE /api/docs
|
|
190
|
+
* Delete a document
|
|
191
|
+
*/
|
|
192
|
+
router.delete('/api/docs', requireAuth, requireScope('ssss:write'), async (req, res) => {
|
|
193
|
+
try {
|
|
194
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
195
|
+
const relPath = req.query.path;
|
|
196
|
+
|
|
197
|
+
if (!relPath || !isSafePath(relPath)) return badRequest(res, 'Invalid path');
|
|
198
|
+
const absPath = path.join(vaultDir, relPath);
|
|
199
|
+
if (!fs.existsSync(absPath)) return notFound(res, 'Document not found');
|
|
200
|
+
|
|
201
|
+
const envelope = {
|
|
202
|
+
type: 'delete',
|
|
203
|
+
idempotency_key: crypto.randomUUID(),
|
|
204
|
+
path: relPath,
|
|
205
|
+
workspace_id: 'default'
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const result = await processOperationAsync(envelope, vaultDir, { agentRole: 'admin' });
|
|
209
|
+
if (!result.success) {
|
|
210
|
+
return res.status(400).json({ error: 'Validation failed', details: result });
|
|
211
|
+
}
|
|
212
|
+
invalidate(vaultDir);
|
|
213
|
+
res.json({ success: true, result });
|
|
214
|
+
} catch (err) { serverError(res, err); }
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Saved Views
|
|
218
|
+
const VIEWS_FILE = path.join(os.homedir(), '.agent', 'config', 'saved-views.json');
|
|
219
|
+
|
|
220
|
+
function getViews() {
|
|
221
|
+
if (!fs.existsSync(VIEWS_FILE)) return [];
|
|
222
|
+
try { return JSON.parse(fs.readFileSync(VIEWS_FILE, 'utf8')); } catch { return []; }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function saveViews(views) {
|
|
226
|
+
if (!fs.existsSync(path.dirname(VIEWS_FILE))) fs.mkdirSync(path.dirname(VIEWS_FILE), { recursive: true });
|
|
227
|
+
// Atomic write
|
|
228
|
+
const temp = VIEWS_FILE + '.tmp';
|
|
229
|
+
fs.writeFileSync(temp, JSON.stringify(views, null, 2), 'utf8');
|
|
230
|
+
fs.renameSync(temp, VIEWS_FILE);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
router.get('/api/views', requireAuth, (req, res) => {
|
|
234
|
+
res.json({ views: getViews() });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
router.post('/api/views', requireAuth, (req, res) => {
|
|
238
|
+
const views = getViews();
|
|
239
|
+
const newView = {
|
|
240
|
+
id: crypto.randomUUID(),
|
|
241
|
+
name: req.body.name || 'Untitled View',
|
|
242
|
+
brain: req.body.brain || 'global',
|
|
243
|
+
filters: req.body.filters || {},
|
|
244
|
+
sort: req.body.sort || {},
|
|
245
|
+
columns: req.body.columns || [],
|
|
246
|
+
createdAt: new Date().toISOString()
|
|
247
|
+
};
|
|
248
|
+
views.push(newView);
|
|
249
|
+
saveViews(views);
|
|
250
|
+
res.json({ view: newView });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
router.delete('/api/views/:id', requireAuth, (req, res) => {
|
|
254
|
+
let views = getViews();
|
|
255
|
+
views = views.filter(v => v.id !== req.params.id);
|
|
256
|
+
saveViews(views);
|
|
257
|
+
res.json({ success: true });
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
export { router as docsRouter };
|