trucontext 0.1.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/LICENSE +21 -0
- package/PRIVACY.md +119 -0
- package/README.md +183 -0
- package/TERMS.md +101 -0
- package/bin/cli.js +157 -0
- package/package.json +49 -0
- package/src/auth.js +197 -0
- package/src/client.js +77 -0
- package/src/commands/apps.js +48 -0
- package/src/commands/contexts.js +56 -0
- package/src/commands/entities.js +141 -0
- package/src/commands/ingest.js +59 -0
- package/src/commands/init.js +81 -0
- package/src/commands/login.js +126 -0
- package/src/commands/logout.js +7 -0
- package/src/commands/query.js +51 -0
- package/src/commands/recall.js +42 -0
- package/src/commands/recipes.js +130 -0
- package/src/commands/relationship-types.js +56 -0
- package/src/commands/schema.js +74 -0
- package/src/commands/whoami.js +23 -0
- package/src/config.js +87 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import select from '@inquirer/select';
|
|
3
|
+
import checkbox from '@inquirer/checkbox';
|
|
4
|
+
import { performLogin } from '../auth.js';
|
|
5
|
+
import { controlPlane } from '../client.js';
|
|
6
|
+
import { setActiveApp, hasAcceptedTerms, recordTermsAcceptance } from '../config.js';
|
|
7
|
+
|
|
8
|
+
async function promptTermsAcceptance() {
|
|
9
|
+
if (hasAcceptedTerms()) return true;
|
|
10
|
+
|
|
11
|
+
console.log(`
|
|
12
|
+
${chalk.bold('Welcome to TruContext')}
|
|
13
|
+
|
|
14
|
+
Before continuing, please review and accept our legal agreements.
|
|
15
|
+
|
|
16
|
+
${chalk.cyan('Key Points:')}
|
|
17
|
+
|
|
18
|
+
${chalk.bold('Your Data')}
|
|
19
|
+
- You own your data. We claim no ownership rights.
|
|
20
|
+
- We will NOT use your data to train AI models without
|
|
21
|
+
your separate, explicit written consent.
|
|
22
|
+
- Your data is isolated to your tenant. Other users
|
|
23
|
+
cannot access it.
|
|
24
|
+
|
|
25
|
+
${chalk.bold('Privacy')}
|
|
26
|
+
- Content you ingest is processed by AI providers
|
|
27
|
+
(Anthropic, Google) via their APIs to operate the
|
|
28
|
+
Service. These providers do not train on API inputs.
|
|
29
|
+
- We do not sell, rent, or share your data with third
|
|
30
|
+
parties for commercial purposes.
|
|
31
|
+
|
|
32
|
+
${chalk.bold('Liability')}
|
|
33
|
+
- The Service is provided "as is" without warranties.
|
|
34
|
+
- Our total liability is limited to fees paid in the
|
|
35
|
+
prior 12 months or $100, whichever is greater.
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
// Offer to view full documents
|
|
39
|
+
const viewChoice = await select({
|
|
40
|
+
message: 'Would you like to read the full legal documents?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'Continue to acceptance', value: 'continue' },
|
|
43
|
+
{ name: 'View Terms of Service', value: 'terms' },
|
|
44
|
+
{ name: 'View Privacy Policy', value: 'privacy' },
|
|
45
|
+
{ name: 'View both', value: 'both' },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (viewChoice !== 'continue') {
|
|
50
|
+
const { default: open } = await import('open');
|
|
51
|
+
if (viewChoice === 'terms' || viewChoice === 'both') {
|
|
52
|
+
console.log(chalk.dim('Opening Terms of Service...'));
|
|
53
|
+
await open('https://trucontext.ai/terms');
|
|
54
|
+
}
|
|
55
|
+
if (viewChoice === 'privacy' || viewChoice === 'both') {
|
|
56
|
+
console.log(chalk.dim('Opening Privacy Policy...'));
|
|
57
|
+
await open('https://trucontext.ai/privacy');
|
|
58
|
+
}
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Checkbox acceptance — both must be checked
|
|
63
|
+
const accepted = await checkbox({
|
|
64
|
+
message: 'To continue, check both boxes:',
|
|
65
|
+
required: true,
|
|
66
|
+
choices: [
|
|
67
|
+
{ name: 'I have read and agree to the Terms of Service', value: 'terms' },
|
|
68
|
+
{ name: 'I have read and agree to the Privacy Policy', value: 'privacy' },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (!accepted.includes('terms') || !accepted.includes('privacy')) {
|
|
73
|
+
console.log(chalk.yellow('\nYou must accept both the Terms of Service and Privacy Policy to use TruContext.'));
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
recordTermsAcceptance();
|
|
78
|
+
console.log(chalk.green('\nTerms accepted.\n'));
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function loginCommand(options) {
|
|
83
|
+
// Terms acceptance gate — must accept before first login
|
|
84
|
+
const accepted = await promptTermsAcceptance();
|
|
85
|
+
if (!accepted) {
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const provider = options.google ? 'Google' : undefined;
|
|
90
|
+
console.log(chalk.dim(`Opening browser for sign-in${provider ? ` (${provider})` : ''}...`));
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const creds = await performLogin({ provider });
|
|
94
|
+
console.log(chalk.green(`\nLogged in as ${chalk.bold(creds.email)}`));
|
|
95
|
+
|
|
96
|
+
// Fetch apps and select one
|
|
97
|
+
try {
|
|
98
|
+
const res = await controlPlane('GET', '/apps');
|
|
99
|
+
const apps = res.data?.apps || [];
|
|
100
|
+
|
|
101
|
+
if (apps.length === 1) {
|
|
102
|
+
setActiveApp(apps[0].appId);
|
|
103
|
+
console.log(chalk.dim(`Active app: ${apps[0].name} (${apps[0].appId})`));
|
|
104
|
+
} else if (apps.length > 1) {
|
|
105
|
+
const chosen = await select({
|
|
106
|
+
message: 'Select an app:',
|
|
107
|
+
choices: apps.map(app => ({
|
|
108
|
+
name: `${app.name} ${chalk.dim(app.appId)}`,
|
|
109
|
+
value: app.appId,
|
|
110
|
+
})),
|
|
111
|
+
});
|
|
112
|
+
setActiveApp(chosen);
|
|
113
|
+
const app = apps.find(a => a.appId === chosen);
|
|
114
|
+
console.log(chalk.green(`Active app: ${app.name}`));
|
|
115
|
+
} else {
|
|
116
|
+
console.log(chalk.dim('\nNo apps yet. Create one with:'));
|
|
117
|
+
console.log(chalk.cyan(' trucontext init "My App"'));
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Non-fatal — login still succeeded
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error(chalk.red(`Login failed: ${err.message}`));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { dataPlane } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export async function queryCommand(question, options) {
|
|
5
|
+
try {
|
|
6
|
+
const body = {
|
|
7
|
+
mode: options.mode === 'context' ? 'CONTEXT' : 'ANSWER',
|
|
8
|
+
max_results: options.limit ? parseInt(options.limit, 10) : 20,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
if (body.mode === 'ANSWER') {
|
|
12
|
+
body.query = question;
|
|
13
|
+
} else {
|
|
14
|
+
body.topic = question;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (options.context) body.context_id = options.context;
|
|
18
|
+
|
|
19
|
+
const res = await dataPlane('POST', '/v1/query', body);
|
|
20
|
+
|
|
21
|
+
// Print answer
|
|
22
|
+
if (res.answer?.summary) {
|
|
23
|
+
console.log(chalk.bold('\nAnswer:'));
|
|
24
|
+
console.log(res.answer.summary);
|
|
25
|
+
if (res.answer.confidence) {
|
|
26
|
+
console.log(chalk.dim(`\nConfidence: ${(res.answer.confidence * 100).toFixed(0)}%`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Print concepts
|
|
31
|
+
if (res.concepts?.length > 0) {
|
|
32
|
+
console.log(chalk.bold('\nConcepts:'));
|
|
33
|
+
for (const c of res.concepts.slice(0, 5)) {
|
|
34
|
+
console.log(` ${c.label} ${chalk.dim(`(${c.evidence_count} evidence)`)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Print people
|
|
39
|
+
if (res.people?.length > 0) {
|
|
40
|
+
console.log(chalk.bold('\nPeople:'));
|
|
41
|
+
for (const p of res.people.slice(0, 5)) {
|
|
42
|
+
console.log(` ${p.name} ${chalk.dim(p.relationship || '')}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.dim(`\n${res.latency_ms}ms`));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(chalk.red(`Query failed: ${err.message}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { dataPlane } from '../client.js';
|
|
3
|
+
|
|
4
|
+
export async function recallCommand(query, options) {
|
|
5
|
+
try {
|
|
6
|
+
const body = {
|
|
7
|
+
query,
|
|
8
|
+
maxResults: options.limit ? parseInt(options.limit, 10) : 10,
|
|
9
|
+
expansionDepth: options.depth ? parseInt(options.depth, 10) : 2,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
if (options.context) body.context_id = options.context;
|
|
13
|
+
|
|
14
|
+
const res = await dataPlane('POST', '/v1/recall', body);
|
|
15
|
+
|
|
16
|
+
// Print synthesis
|
|
17
|
+
if (res.synthesis?.summary) {
|
|
18
|
+
console.log(chalk.bold('\nSynthesis:'));
|
|
19
|
+
console.log(res.synthesis.summary);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Print seeds
|
|
23
|
+
if (res.seeds?.length > 0) {
|
|
24
|
+
console.log(chalk.bold(`\nSeeds (${res.seeds.length}):`));
|
|
25
|
+
for (const s of res.seeds) {
|
|
26
|
+
const label = s.snippet || s.label || s.id;
|
|
27
|
+
const score = (s.score * 100).toFixed(0);
|
|
28
|
+
console.log(` ${chalk.dim(`${score}%`)} ${label.slice(0, 100)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Print graph context summary
|
|
33
|
+
if (res.context) {
|
|
34
|
+
console.log(chalk.dim(`\nGraph: ${res.context.nodes?.length || 0} nodes, ${res.context.edges?.length || 0} edges`));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.dim(`${res.latency_ms}ms`));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(chalk.red(`Recall failed: ${err.message}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { controlPlane } from '../client.js';
|
|
3
|
+
import { getActiveApp } from '../config.js';
|
|
4
|
+
|
|
5
|
+
function requireApp() {
|
|
6
|
+
const appId = getActiveApp();
|
|
7
|
+
if (!appId) {
|
|
8
|
+
console.error(chalk.red('No active app. Run: trucontext use <app>'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
return appId;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function recipesListCommand() {
|
|
15
|
+
try {
|
|
16
|
+
const appId = requireApp();
|
|
17
|
+
const res = await controlPlane('GET', `/apps/${appId}/recipes`);
|
|
18
|
+
const recipes = res.data?.recipes || [];
|
|
19
|
+
|
|
20
|
+
if (recipes.length === 0) {
|
|
21
|
+
console.log(chalk.yellow('No recipes found.'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
for (const r of recipes) {
|
|
26
|
+
console.log(`${chalk.bold(r.recipeId)} ${chalk.dim(`[${r.type || 'custom'}]`)}`);
|
|
27
|
+
if (r.name) console.log(` ${r.name}`);
|
|
28
|
+
}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function recipesGetCommand(recipeId) {
|
|
36
|
+
try {
|
|
37
|
+
const appId = requireApp();
|
|
38
|
+
const res = await controlPlane('GET', `/apps/${appId}/recipes/${recipeId}`);
|
|
39
|
+
const r = res.data || res;
|
|
40
|
+
|
|
41
|
+
console.log(chalk.bold(r.recipeId || recipeId));
|
|
42
|
+
if (r.name) console.log(` name: ${r.name}`);
|
|
43
|
+
if (r.type) console.log(` type: ${chalk.dim(r.type)}`);
|
|
44
|
+
|
|
45
|
+
// Show interpretation (WHY) — present on both system and custom recipes
|
|
46
|
+
if (r.interpretation) {
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(chalk.bold('Interpretation:'));
|
|
49
|
+
console.log(` ${r.interpretation}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// System recipes have operational details (HOW)
|
|
53
|
+
if (r.type === 'system') {
|
|
54
|
+
if (r.entity_setup) {
|
|
55
|
+
console.log();
|
|
56
|
+
console.log(chalk.bold('Entity Setup:'));
|
|
57
|
+
console.log(` ${chalk.dim(JSON.stringify(r.entity_setup, null, 2).replace(/\n/g, '\n '))}`);
|
|
58
|
+
}
|
|
59
|
+
if (r.ingest_patterns) {
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(chalk.bold('Ingest Patterns:'));
|
|
62
|
+
console.log(` ${chalk.dim(JSON.stringify(r.ingest_patterns, null, 2).replace(/\n/g, '\n '))}`);
|
|
63
|
+
}
|
|
64
|
+
if (r.query_patterns) {
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(chalk.bold('Query Patterns:'));
|
|
67
|
+
console.log(` ${chalk.dim(JSON.stringify(r.query_patterns, null, 2).replace(/\n/g, '\n '))}`);
|
|
68
|
+
}
|
|
69
|
+
if (r.lifecycle) {
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(chalk.bold('Lifecycle:'));
|
|
72
|
+
console.log(` ${chalk.dim(JSON.stringify(r.lifecycle, null, 2).replace(/\n/g, '\n '))}`);
|
|
73
|
+
}
|
|
74
|
+
if (r.example_scenario) {
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(chalk.bold('Example Scenario:'));
|
|
77
|
+
console.log(` ${r.example_scenario}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function recipesCreateCommand(options) {
|
|
87
|
+
try {
|
|
88
|
+
const appId = requireApp();
|
|
89
|
+
|
|
90
|
+
if (!options.id) {
|
|
91
|
+
console.error(chalk.red('--id is required'));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (!options.name) {
|
|
95
|
+
console.error(chalk.red('--name is required'));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
if (!options.purpose) {
|
|
99
|
+
console.error(chalk.red('--purpose is required'));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const body = {
|
|
104
|
+
recipeId: options.id,
|
|
105
|
+
name: options.name,
|
|
106
|
+
purpose: options.purpose,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (options.decayProfile) body.decay_profile = options.decayProfile;
|
|
110
|
+
if (options.confidenceBias) body.confidence_bias = options.confidenceBias;
|
|
111
|
+
|
|
112
|
+
const res = await controlPlane('POST', `/apps/${appId}/recipes`, body);
|
|
113
|
+
const r = res.data || res;
|
|
114
|
+
console.log(chalk.green(`Created: ${chalk.bold(r.recipeId || options.id)}`));
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function recipesDeleteCommand(recipeId) {
|
|
122
|
+
try {
|
|
123
|
+
const appId = requireApp();
|
|
124
|
+
await controlPlane('DELETE', `/apps/${appId}/recipes/${recipeId}`);
|
|
125
|
+
console.log(chalk.green(`Deleted: ${recipeId}`));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { controlPlane } from '../client.js';
|
|
3
|
+
import { getActiveApp } from '../config.js';
|
|
4
|
+
|
|
5
|
+
export async function relationshipTypesListCommand(options) {
|
|
6
|
+
try {
|
|
7
|
+
const appId = getActiveApp();
|
|
8
|
+
if (!appId) {
|
|
9
|
+
console.error(chalk.red('No active app. Run: trucontext use <app>'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const params = new URLSearchParams();
|
|
14
|
+
if (options.category) params.set('category', options.category);
|
|
15
|
+
const qs = params.toString();
|
|
16
|
+
const path = `/apps/${appId}/relationship-types${qs ? `?${qs}` : ''}`;
|
|
17
|
+
|
|
18
|
+
const res = await controlPlane('GET', path);
|
|
19
|
+
const types = res.data?.relationshipTypes || res.data || [];
|
|
20
|
+
|
|
21
|
+
if (Array.isArray(types) && types.length === 0) {
|
|
22
|
+
console.log(chalk.yellow('No relationship types found.'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Group by category if present
|
|
27
|
+
if (Array.isArray(types)) {
|
|
28
|
+
const grouped = {};
|
|
29
|
+
for (const t of types) {
|
|
30
|
+
const cat = t.category || 'uncategorized';
|
|
31
|
+
if (!grouped[cat]) grouped[cat] = [];
|
|
32
|
+
grouped[cat].push(t);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const [category, items] of Object.entries(grouped)) {
|
|
36
|
+
console.log(chalk.bold(category));
|
|
37
|
+
for (const t of items) {
|
|
38
|
+
const desc = t.description ? ` — ${t.description}` : '';
|
|
39
|
+
console.log(` ${chalk.dim(t.type || t.relationshipType)}${desc}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} else if (typeof types === 'object') {
|
|
43
|
+
// Already grouped by category
|
|
44
|
+
for (const [category, items] of Object.entries(types)) {
|
|
45
|
+
console.log(chalk.bold(category));
|
|
46
|
+
for (const t of items) {
|
|
47
|
+
const desc = t.description ? ` — ${t.description}` : '';
|
|
48
|
+
console.log(` ${chalk.dim(t.type || t.relationshipType)}${desc}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { controlPlane } from '../client.js';
|
|
3
|
+
import { getActiveApp } from '../config.js';
|
|
4
|
+
|
|
5
|
+
function requireActiveApp() {
|
|
6
|
+
const appId = getActiveApp();
|
|
7
|
+
if (!appId) {
|
|
8
|
+
console.error(chalk.red('No active app. Run: trucontext use <app-id>'));
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
return appId;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function schemaShowCommand() {
|
|
15
|
+
const appId = requireActiveApp();
|
|
16
|
+
try {
|
|
17
|
+
const res = await controlPlane('GET', `/apps/${appId}/schema`);
|
|
18
|
+
const schema = res.data;
|
|
19
|
+
|
|
20
|
+
if (!schema) {
|
|
21
|
+
console.log(chalk.yellow('No schema configured. Generate one with: trucontext schema generate'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.bold('Schema:'), schema.name || appId);
|
|
26
|
+
console.log(chalk.bold('Version:'), schema.version || 'v1');
|
|
27
|
+
|
|
28
|
+
if (schema.custom_node_types?.length > 0) {
|
|
29
|
+
console.log(chalk.bold('\nNode Types:'));
|
|
30
|
+
for (const t of schema.custom_node_types) {
|
|
31
|
+
console.log(` ${chalk.cyan(t.name)} — ${t.description || ''}`);
|
|
32
|
+
if (t.properties?.length > 0) {
|
|
33
|
+
console.log(` ${chalk.dim(t.properties.join(', '))}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (schema.custom_edge_types?.length > 0) {
|
|
39
|
+
console.log(chalk.bold('\nEdge Types:'));
|
|
40
|
+
console.log(` ${chalk.dim(schema.custom_edge_types.join(', '))}`);
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function schemaGenerateCommand(options) {
|
|
49
|
+
const appId = requireActiveApp();
|
|
50
|
+
try {
|
|
51
|
+
if (!options.description) {
|
|
52
|
+
console.error(chalk.red('Description required: trucontext schema generate --description "..."'));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(chalk.dim('Generating schema...'));
|
|
57
|
+
const res = await controlPlane('POST', '/schema/generate', {
|
|
58
|
+
description: options.description,
|
|
59
|
+
appName: options.name || appId,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log(chalk.green('Schema generated.'));
|
|
63
|
+
const schema = res.data;
|
|
64
|
+
if (schema?.custom_node_types) {
|
|
65
|
+
console.log(chalk.bold('Node types:'), schema.custom_node_types.map(t => t.name).join(', '));
|
|
66
|
+
}
|
|
67
|
+
if (schema?.custom_edge_types) {
|
|
68
|
+
console.log(chalk.bold('Edge types:'), schema.custom_edge_types.join(', '));
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(chalk.red(`Failed: ${err.message}`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getCredentials, getActiveApp } from '../config.js';
|
|
3
|
+
|
|
4
|
+
export async function whoamiCommand() {
|
|
5
|
+
const creds = getCredentials();
|
|
6
|
+
if (!creds) {
|
|
7
|
+
console.log(chalk.yellow('Not logged in. Run: trucontext login'));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
console.log(chalk.bold('Email:'), creds.email);
|
|
12
|
+
console.log(chalk.bold('Sub:'), chalk.dim(creds.sub));
|
|
13
|
+
|
|
14
|
+
const activeApp = getActiveApp();
|
|
15
|
+
if (activeApp) {
|
|
16
|
+
console.log(chalk.bold('Active app:'), activeApp);
|
|
17
|
+
} else {
|
|
18
|
+
console.log(chalk.bold('Active app:'), chalk.yellow('none — run: trucontext use <app-id>'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const expiresIn = Math.max(0, Math.round((creds.expiresAt - Date.now()) / 1000));
|
|
22
|
+
console.log(chalk.bold('Token expires in:'), expiresIn > 0 ? `${expiresIn}s` : chalk.red('expired'));
|
|
23
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// config.js — Credential and configuration management
|
|
2
|
+
// Stores credentials and config in ~/.trucontext/
|
|
3
|
+
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync, chmodSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.trucontext');
|
|
9
|
+
const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
|
|
10
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
11
|
+
|
|
12
|
+
// Cognito + API endpoints
|
|
13
|
+
export const COGNITO_DOMAIN = 'https://auth.trucontext.ai';
|
|
14
|
+
export const CLI_CLIENT_ID = '4tn43b2p32bh3t9tcstrrsi7fo';
|
|
15
|
+
export const CALLBACK_PORT = 8181;
|
|
16
|
+
export const CALLBACK_URL = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
17
|
+
export const DATA_PLANE_URL = 'https://api.trucontext.ai';
|
|
18
|
+
export const CONTROL_PLANE_URL = 'https://platform.trucontext.ai';
|
|
19
|
+
|
|
20
|
+
function ensureDir() {
|
|
21
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
22
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// --- Credentials ---
|
|
27
|
+
|
|
28
|
+
export function getCredentials() {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function saveCredentials(creds) {
|
|
37
|
+
ensureDir();
|
|
38
|
+
writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2));
|
|
39
|
+
chmodSync(CREDENTIALS_FILE, 0o600);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function clearCredentials() {
|
|
43
|
+
try {
|
|
44
|
+
unlinkSync(CREDENTIALS_FILE);
|
|
45
|
+
} catch {
|
|
46
|
+
// Already gone
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Config ---
|
|
51
|
+
|
|
52
|
+
export function getConfig() {
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
|
|
55
|
+
} catch {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function saveConfig(config) {
|
|
61
|
+
ensureDir();
|
|
62
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getActiveApp() {
|
|
66
|
+
return getConfig().activeApp || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function setActiveApp(appId) {
|
|
70
|
+
const config = getConfig();
|
|
71
|
+
config.activeApp = appId;
|
|
72
|
+
saveConfig(config);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// --- Terms Acceptance ---
|
|
76
|
+
|
|
77
|
+
export function hasAcceptedTerms() {
|
|
78
|
+
const config = getConfig();
|
|
79
|
+
return !!config.termsAcceptedAt;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function recordTermsAcceptance() {
|
|
83
|
+
const config = getConfig();
|
|
84
|
+
config.termsAcceptedAt = new Date().toISOString();
|
|
85
|
+
config.termsVersion = '2026-03-22';
|
|
86
|
+
saveConfig(config);
|
|
87
|
+
}
|