trucontext 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/bin/cli.js +19 -3
- package/package.json +1 -1
- package/src/client.js +15 -0
- package/src/commands/docs.js +113 -0
- package/src/commands/entities.js +91 -16
package/README.md
CHANGED
|
@@ -168,6 +168,8 @@ Observe → Capture → Sleep → Wake → Query → Observe → ...
|
|
|
168
168
|
| `relationship-types` | List all relationship types |
|
|
169
169
|
| `schema show` | Show current app schema |
|
|
170
170
|
| `schema generate` | AI-generate a schema from description |
|
|
171
|
+
| `docs` | List all API documentation sections |
|
|
172
|
+
| `docs show <slug>` | View a specific doc section (no auth required) |
|
|
171
173
|
|
|
172
174
|
## API
|
|
173
175
|
|
package/bin/cli.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
3
4
|
import { program } from 'commander';
|
|
4
5
|
import { loginCommand } from '../src/commands/login.js';
|
|
6
|
+
|
|
7
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
5
8
|
import { logoutCommand } from '../src/commands/logout.js';
|
|
6
9
|
import { whoamiCommand } from '../src/commands/whoami.js';
|
|
7
10
|
import { appsCommand, useCommand } from '../src/commands/apps.js';
|
|
@@ -11,14 +14,15 @@ import { queryCommand } from '../src/commands/query.js';
|
|
|
11
14
|
import { recallCommand } from '../src/commands/recall.js';
|
|
12
15
|
import { contextsListCommand, contextsCreateCommand, contextsDeleteCommand } from '../src/commands/contexts.js';
|
|
13
16
|
import { schemaShowCommand, schemaGenerateCommand, schemaSetAuthorshipCommand } from '../src/commands/schema.js';
|
|
14
|
-
import { entitiesListCommand, entitiesGetCommand, entitiesCreateCommand, entitiesUpdateCommand, entitiesDeleteCommand } from '../src/commands/entities.js';
|
|
17
|
+
import { entitiesListCommand, entitiesGetCommand, entitiesCreateCommand, entitiesUpdateCommand, entitiesDeleteCommand, entitiesEdgesCommand } from '../src/commands/entities.js';
|
|
15
18
|
import { recipesListCommand, recipesGetCommand, recipesCreateCommand, recipesDeleteCommand } from '../src/commands/recipes.js';
|
|
16
19
|
import { relationshipTypesListCommand } from '../src/commands/relationship-types.js';
|
|
20
|
+
import { docsListCommand, docsShowCommand } from '../src/commands/docs.js';
|
|
17
21
|
|
|
18
22
|
program
|
|
19
23
|
.name('trucontext')
|
|
20
24
|
.description('TruContext CLI — contextual memory for AI applications')
|
|
21
|
-
.version(
|
|
25
|
+
.version(pkg.version);
|
|
22
26
|
|
|
23
27
|
// Auth
|
|
24
28
|
program.command('login')
|
|
@@ -88,7 +92,9 @@ const entities = program.command('entities').description('Manage entities').acti
|
|
|
88
92
|
entities.command('list')
|
|
89
93
|
.description('List entities')
|
|
90
94
|
.option('--type <type>', 'Filter by entity type')
|
|
91
|
-
.option('
|
|
95
|
+
.option('--context <id>', 'Filter by context')
|
|
96
|
+
.option('--scope <scope>', 'Filter by scope')
|
|
97
|
+
.option('-l, --limit <n>', 'Max results (default: 50)')
|
|
92
98
|
.action(entitiesListCommand);
|
|
93
99
|
entities.command('get <entityId>')
|
|
94
100
|
.description('Get an entity')
|
|
@@ -99,6 +105,7 @@ entities.command('create')
|
|
|
99
105
|
.requiredOption('--type <Type>', 'Entity type (PascalCase)')
|
|
100
106
|
.option('--properties <json>', 'JSON properties object')
|
|
101
107
|
.option('--recipe <recipeId>', 'Recipe ID')
|
|
108
|
+
.option('--heartbeat', 'Enable heartbeat')
|
|
102
109
|
.option('--no-heartbeat', 'Disable heartbeat')
|
|
103
110
|
.option('--confidence <n>', 'Confidence level (0.0-1.0)')
|
|
104
111
|
.option('--temporal', 'Content can decay over time (default)')
|
|
@@ -114,10 +121,14 @@ entities.command('update <entityId>')
|
|
|
114
121
|
.option('--confidence <n>', 'Confidence level (0.0-1.0)')
|
|
115
122
|
.option('--temporal', 'Content can decay over time')
|
|
116
123
|
.option('--no-temporal', 'Content is a permanent fact')
|
|
124
|
+
.option('-c, --context <entry>', 'Link to context (entity-id:RELATIONSHIP, repeatable)', (val, prev) => [...prev, val], [])
|
|
117
125
|
.action(entitiesUpdateCommand);
|
|
118
126
|
entities.command('delete <entityId>')
|
|
119
127
|
.description('Delete an entity')
|
|
120
128
|
.action(entitiesDeleteCommand);
|
|
129
|
+
entities.command('edges <entityId>')
|
|
130
|
+
.description('List edges for an entity')
|
|
131
|
+
.action(entitiesEdgesCommand);
|
|
121
132
|
|
|
122
133
|
// Recipes
|
|
123
134
|
const recipes = program.command('recipes').description('Manage recipes').action(function() { this.help(); });
|
|
@@ -158,4 +169,9 @@ schema.command('set-authorship <model>')
|
|
|
158
169
|
.description('Set authorship model (single_author, multi_author, anonymous)')
|
|
159
170
|
.action(schemaSetAuthorshipCommand);
|
|
160
171
|
|
|
172
|
+
// Docs (public — no auth required)
|
|
173
|
+
const docs = program.command('docs').description('Browse API documentation').action(docsListCommand);
|
|
174
|
+
docs.command('list').description('List all doc sections').action(docsListCommand);
|
|
175
|
+
docs.command('show <slug>').description('Show a specific doc section').action(docsShowCommand);
|
|
176
|
+
|
|
161
177
|
program.parse();
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -75,3 +75,18 @@ export function dataPlane(method, path, body) {
|
|
|
75
75
|
export function controlPlane(method, path, body) {
|
|
76
76
|
return request(CONTROL_PLANE_URL, method, path, body);
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
export async function publicApi(method, path) {
|
|
80
|
+
const ac = new AbortController();
|
|
81
|
+
const timeout = setTimeout(() => ac.abort(), 15000);
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(`${CONTROL_PLANE_URL}${path}`, {
|
|
84
|
+
method,
|
|
85
|
+
headers: { 'Content-Type': 'application/json' },
|
|
86
|
+
signal: ac.signal,
|
|
87
|
+
});
|
|
88
|
+
return handleResponse(res);
|
|
89
|
+
} finally {
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { publicApi } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export async function docsListCommand() {
|
|
5
|
+
try {
|
|
6
|
+
const res = await publicApi('GET', '/public/docs');
|
|
7
|
+
const sections = res.data?.sections || res.sections || [];
|
|
8
|
+
|
|
9
|
+
if (sections.length === 0) {
|
|
10
|
+
console.log(chalk.yellow('No documentation available.'));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(chalk.bold('TruContext Documentation\n'));
|
|
15
|
+
for (const section of sections) {
|
|
16
|
+
const slug = section.sectionId || section.layerId || section.slug || section.id;
|
|
17
|
+
console.log(` ${chalk.cyan(slug.padEnd(28))} ${chalk.bold(section.title)}`);
|
|
18
|
+
}
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(chalk.dim(`${sections.length} sections. Use: trucontext docs show <slug>`));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function docsShowCommand(slug) {
|
|
28
|
+
try {
|
|
29
|
+
const res = await publicApi('GET', `/public/docs/${slug}`);
|
|
30
|
+
const doc = res.data || res;
|
|
31
|
+
|
|
32
|
+
if (!doc || !doc.title) {
|
|
33
|
+
console.error(chalk.red(`No doc found for slug: ${slug}`));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Title
|
|
38
|
+
console.log(chalk.bold.underline(`\n${doc.title}\n`));
|
|
39
|
+
|
|
40
|
+
// Main content
|
|
41
|
+
if (doc.content) {
|
|
42
|
+
console.log(renderMarkdown(doc.content));
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Callouts
|
|
47
|
+
for (const callout of (doc.callouts || [])) {
|
|
48
|
+
const icon = callout.variant === 'warning' ? '!' : 'i';
|
|
49
|
+
console.log(chalk.yellow(` [${icon}] ${chalk.bold(callout.title)}`));
|
|
50
|
+
console.log(` ${chalk.dim(callout.content.replace(/\n/g, '\n '))}`);
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Subsections
|
|
55
|
+
for (const sub of (doc.subsections || [])) {
|
|
56
|
+
if (sub.title) {
|
|
57
|
+
console.log(chalk.cyan.bold(`## ${sub.title}`));
|
|
58
|
+
}
|
|
59
|
+
if (sub.content) {
|
|
60
|
+
console.log(renderMarkdown(sub.content));
|
|
61
|
+
console.log();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Steps
|
|
65
|
+
for (const step of (sub.steps || [])) {
|
|
66
|
+
console.log(` ${chalk.green(step.number + '.')} ${renderMarkdown(step.content)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Endpoints
|
|
70
|
+
for (const ep of (sub.endpoints || [])) {
|
|
71
|
+
console.log(` ${chalk.green(ep.method)} ${chalk.bold(ep.path)}`);
|
|
72
|
+
if (ep.description) console.log(` ${chalk.dim(ep.description)}`);
|
|
73
|
+
if (ep.auth) console.log(` ${chalk.dim(`Auth: ${ep.auth}`)}`);
|
|
74
|
+
if (ep.body) {
|
|
75
|
+
console.log(` ${chalk.dim('Body:')} ${JSON.stringify(ep.body, null, 2).split('\n').join('\n ')}`);
|
|
76
|
+
}
|
|
77
|
+
if (ep.response) {
|
|
78
|
+
console.log(` ${chalk.dim('Response:')} ${JSON.stringify(ep.response, null, 2).split('\n').join('\n ')}`);
|
|
79
|
+
}
|
|
80
|
+
console.log();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Code blocks
|
|
84
|
+
for (const block of (sub.codeBlocks || [])) {
|
|
85
|
+
if (block.title) console.log(` ${chalk.dim(block.title)}`);
|
|
86
|
+
console.log(chalk.green(` ${block.code}`));
|
|
87
|
+
console.log();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Items (relationship types, node types, etc.)
|
|
91
|
+
for (const item of (sub.items || [])) {
|
|
92
|
+
const label = item.type || item.name || item.recipeId || '';
|
|
93
|
+
const desc = item.description || '';
|
|
94
|
+
if (label) {
|
|
95
|
+
console.log(` ${chalk.yellow(label.padEnd(24))} ${desc}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (sub.steps?.length || sub.items?.length) console.log();
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Simple markdown-to-terminal renderer
|
|
108
|
+
function renderMarkdown(text) {
|
|
109
|
+
return text
|
|
110
|
+
.replace(/\*\*(.+?)\*\*/g, (_, m) => chalk.bold(m))
|
|
111
|
+
.replace(/`(.+?)`/g, (_, m) => chalk.green(m))
|
|
112
|
+
.replace(/\[(.+?)\]\(.+?\)/g, (_, m) => chalk.underline(m));
|
|
113
|
+
}
|
package/src/commands/entities.js
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { dataPlane } from '../client.js';
|
|
3
3
|
|
|
4
|
+
// Fields stored on the node but not user-declared properties
|
|
5
|
+
const SYSTEM_FIELDS = new Set([
|
|
6
|
+
'tenantId', 'entityId', 'provenance', 'scope', 'createdAt', 'updatedAt',
|
|
7
|
+
'lastReinforcedAt', 'confidence', 'temporal', 'heartbeat_enabled', 'recipe_id',
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
function displayEntity(e) {
|
|
11
|
+
const props = e.properties || {};
|
|
12
|
+
const type = e.type || e.labels?.[0] || 'Unknown';
|
|
13
|
+
const entityId = e.entityId || props.entityId;
|
|
14
|
+
const confidence = e.confidence ?? props.confidence;
|
|
15
|
+
const temporal = e.temporal ?? props.temporal;
|
|
16
|
+
const recipeId = e.recipe_id ?? props.recipe_id;
|
|
17
|
+
const heartbeat = e.heartbeat_enabled ?? props.heartbeat_enabled;
|
|
18
|
+
const provenance = e.provenance ?? props.provenance;
|
|
19
|
+
|
|
20
|
+
console.log(`${chalk.bold(entityId)} ${chalk.dim(`[${type}]`)}`);
|
|
21
|
+
if (provenance) console.log(` provenance: ${chalk.dim(provenance)}`);
|
|
22
|
+
if (recipeId) console.log(` recipe: ${chalk.cyan(recipeId)}`);
|
|
23
|
+
if (confidence !== undefined) console.log(` confidence: ${chalk.dim(confidence)}`);
|
|
24
|
+
if (temporal !== undefined) console.log(` temporal: ${chalk.dim(temporal)}`);
|
|
25
|
+
if (heartbeat !== undefined) console.log(` heartbeat: ${chalk.dim(heartbeat)}`);
|
|
26
|
+
|
|
27
|
+
// Show user-declared properties (exclude system fields)
|
|
28
|
+
const userProps = {};
|
|
29
|
+
for (const [k, v] of Object.entries(props)) {
|
|
30
|
+
if (!SYSTEM_FIELDS.has(k) && v !== null && v !== undefined) {
|
|
31
|
+
userProps[k] = v;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Object.keys(userProps).length > 0) {
|
|
35
|
+
console.log(` properties: ${chalk.dim(JSON.stringify(userProps))}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
4
39
|
export async function entitiesListCommand(options) {
|
|
5
40
|
try {
|
|
6
41
|
const params = new URLSearchParams();
|
|
7
42
|
if (options.type) params.set('type', options.type);
|
|
43
|
+
if (options.context) params.set('context_id', options.context);
|
|
44
|
+
if (options.scope) params.set('scope', options.scope);
|
|
8
45
|
if (options.limit) params.set('limit', options.limit);
|
|
9
46
|
const qs = params.toString();
|
|
10
47
|
const path = `/v1/entities${qs ? `?${qs}` : ''}`;
|
|
@@ -18,15 +55,9 @@ export async function entitiesListCommand(options) {
|
|
|
18
55
|
}
|
|
19
56
|
|
|
20
57
|
for (const e of entities) {
|
|
21
|
-
|
|
22
|
-
if (e.recipe_id) console.log(` recipe: ${chalk.dim(e.recipe_id)}`);
|
|
23
|
-
if (e.properties && Object.keys(e.properties).length > 0) {
|
|
24
|
-
console.log(` properties: ${chalk.dim(JSON.stringify(e.properties))}`);
|
|
25
|
-
}
|
|
26
|
-
if (e.heartbeat_enabled !== undefined) console.log(` heartbeat: ${chalk.dim(e.heartbeat_enabled)}`);
|
|
27
|
-
if (e.confidence !== undefined) console.log(` confidence: ${chalk.dim(e.confidence)}`);
|
|
28
|
-
if (e.temporal !== undefined) console.log(` temporal: ${chalk.dim(e.temporal)}`);
|
|
58
|
+
displayEntity(e);
|
|
29
59
|
}
|
|
60
|
+
console.log(chalk.dim(`\n${entities.length} entities`));
|
|
30
61
|
} catch (err) {
|
|
31
62
|
console.error(chalk.red(`Failed: ${err.message}`));
|
|
32
63
|
process.exit(1);
|
|
@@ -37,15 +68,22 @@ export async function entitiesGetCommand(entityId) {
|
|
|
37
68
|
try {
|
|
38
69
|
const res = await dataPlane('GET', `/v1/entities/${entityId}`);
|
|
39
70
|
const e = res.data || res;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
displayEntity(e);
|
|
72
|
+
|
|
73
|
+
// Show edges if available
|
|
74
|
+
try {
|
|
75
|
+
const edgeRes = await dataPlane('GET', `/v1/entities/${entityId}/edges`);
|
|
76
|
+
const edges = edgeRes.data?.edges || edgeRes.data || [];
|
|
77
|
+
if (edges.length > 0) {
|
|
78
|
+
console.log(chalk.dim(`\n Edges (${edges.length}):`));
|
|
79
|
+
for (const edge of edges) {
|
|
80
|
+
const dir = edge.direction === 'outgoing' ? '→' : '←';
|
|
81
|
+
console.log(` ${dir} ${chalk.cyan(edge.type)} ${chalk.dim(edge.targetEntityId || edge.targetLabel || edge.target || '')}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Edge listing may not be available — skip silently
|
|
45
86
|
}
|
|
46
|
-
if (e.heartbeat_enabled !== undefined) console.log(` heartbeat: ${chalk.dim(e.heartbeat_enabled)}`);
|
|
47
|
-
if (e.confidence !== undefined) console.log(` confidence: ${chalk.dim(e.confidence)}`);
|
|
48
|
-
if (e.temporal !== undefined) console.log(` temporal: ${chalk.dim(e.temporal)}`);
|
|
49
87
|
} catch (err) {
|
|
50
88
|
console.error(chalk.red(`Failed: ${err.message}`));
|
|
51
89
|
process.exit(1);
|
|
@@ -122,6 +160,19 @@ export async function entitiesUpdateCommand(entityId, options) {
|
|
|
122
160
|
if (options.confidence !== undefined) body.confidence = parseFloat(options.confidence);
|
|
123
161
|
if (options.temporal !== undefined) body.temporal = options.temporal;
|
|
124
162
|
|
|
163
|
+
if (options.context?.length > 0) {
|
|
164
|
+
body.contexts = options.context.map(entry => {
|
|
165
|
+
const colonIdx = entry.indexOf(':');
|
|
166
|
+
if (colonIdx === -1) {
|
|
167
|
+
return { context_id: entry, relationship: 'ABOUT' };
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
context_id: entry.slice(0, colonIdx),
|
|
171
|
+
relationship: entry.slice(colonIdx + 1),
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
125
176
|
await dataPlane('PUT', `/v1/entities/${entityId}`, body);
|
|
126
177
|
console.log(chalk.green(`Updated: ${entityId}`));
|
|
127
178
|
} catch (err) {
|
|
@@ -139,3 +190,27 @@ export async function entitiesDeleteCommand(entityId) {
|
|
|
139
190
|
process.exit(1);
|
|
140
191
|
}
|
|
141
192
|
}
|
|
193
|
+
|
|
194
|
+
export async function entitiesEdgesCommand(entityId) {
|
|
195
|
+
try {
|
|
196
|
+
const res = await dataPlane('GET', `/v1/entities/${entityId}/edges`);
|
|
197
|
+
const edges = res.data?.edges || res.data || [];
|
|
198
|
+
|
|
199
|
+
if (edges.length === 0) {
|
|
200
|
+
console.log(chalk.yellow('No edges found.'));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const edge of edges) {
|
|
205
|
+
const dir = edge.direction === 'outgoing' ? '→' : '←';
|
|
206
|
+
console.log(` ${dir} ${chalk.cyan(edge.type)} ${chalk.dim(edge.targetEntityId || edge.targetLabel || '')}`);
|
|
207
|
+
if (edge.properties && Object.keys(edge.properties).length > 0) {
|
|
208
|
+
console.log(` ${chalk.dim(JSON.stringify(edge.properties))}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
console.log(chalk.dim(`\n${edges.length} edges`));
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
}
|