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 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('0.2.1');
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('-l, --limit <n>', 'Max results')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trucontext",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "TruContext CLI — contextual memory for AI applications",
5
5
  "type": "module",
6
6
  "bin": {
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
+ }
@@ -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
- console.log(`${chalk.bold(e.entityId)} ${chalk.dim(`[${e.type}]`)}`);
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
- console.log(`${chalk.bold(e.entityId)} ${chalk.dim(`[${e.type}]`)}`);
42
- if (e.recipe_id) console.log(` recipe: ${chalk.dim(e.recipe_id)}`);
43
- if (e.properties && Object.keys(e.properties).length > 0) {
44
- console.log(` properties: ${chalk.dim(JSON.stringify(e.properties))}`);
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
+ }