@hsafa/cli 0.0.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,155 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import api from '../utils/api.js';
4
+ import { graphqlRequest } from '../utils/graphql.js';
5
+ import ora from 'ora';
6
+ export function registerKbCommands(program) {
7
+ const kb = program.command('kb').description('Knowledge Base management');
8
+ kb
9
+ .command('list')
10
+ .description('List your knowledge bases')
11
+ .action(async () => {
12
+ const spinner = ora('Fetching knowledge bases...').start();
13
+ try {
14
+ const query = `
15
+ query {
16
+ myKnowledgeBases {
17
+ id
18
+ name
19
+ description
20
+ createdAt
21
+ }
22
+ }
23
+ `;
24
+ const data = await graphqlRequest(query);
25
+ const kbs = data.myKnowledgeBases;
26
+ spinner.stop();
27
+ if (kbs.length === 0) {
28
+ console.log(chalk.yellow('No knowledge bases found.'));
29
+ return;
30
+ }
31
+ const table = new Table({
32
+ head: [chalk.cyan('ID'), chalk.cyan('Name'), chalk.cyan('Description'), chalk.cyan('Created')],
33
+ colWidths: [36, 20, 30, 25]
34
+ });
35
+ kbs.forEach((k) => {
36
+ table.push([k.id, k.name, k.description || 'N/A', k.createdAt]);
37
+ });
38
+ console.log(table.toString());
39
+ }
40
+ catch (error) {
41
+ spinner.fail(`Failed to fetch KB: ${error.message}`);
42
+ }
43
+ });
44
+ kb
45
+ .command('docs <id>')
46
+ .description('List documents in a knowledge base')
47
+ .action(async (id) => {
48
+ const spinner = ora('Fetching documents...').start();
49
+ try {
50
+ const query = `
51
+ query($id: ID!) {
52
+ knowledgeBaseDocuments(knowledgeBaseId: $id) {
53
+ id
54
+ fileName
55
+ fileType
56
+ status
57
+ uploadedAt
58
+ }
59
+ }
60
+ `;
61
+ const data = await graphqlRequest(query, { id });
62
+ const docs = data.knowledgeBaseDocuments;
63
+ spinner.stop();
64
+ if (docs.length === 0) {
65
+ console.log(chalk.yellow('No documents found in this KB.'));
66
+ return;
67
+ }
68
+ const table = new Table({
69
+ head: [chalk.cyan('ID'), chalk.cyan('File Name'), chalk.cyan('Status'), chalk.cyan('Uploaded')],
70
+ colWidths: [36, 30, 15, 25]
71
+ });
72
+ docs.forEach((d) => {
73
+ table.push([d.id, d.fileName, d.status, d.uploadedAt]);
74
+ });
75
+ console.log(table.toString());
76
+ }
77
+ catch (error) {
78
+ spinner.fail(`Failed to fetch documents: ${error.message}`);
79
+ }
80
+ });
81
+ kb
82
+ .command('create <name>')
83
+ .description('Create a new knowledge base')
84
+ .option('-d, --description <desc>', 'KB description')
85
+ .action(async (name, options) => {
86
+ const spinner = ora('Creating knowledge base...').start();
87
+ try {
88
+ const mutation = `
89
+ mutation($input: CreateKnowledgeBaseInput!) {
90
+ createKnowledgeBase(input: $input) {
91
+ id
92
+ name
93
+ }
94
+ }
95
+ `;
96
+ const data = await graphqlRequest(mutation, {
97
+ input: {
98
+ name,
99
+ description: options.description || '',
100
+ }
101
+ });
102
+ spinner.succeed(chalk.green(`Knowledge base "${data.createKnowledgeBase.name}" created with ID: ${data.createKnowledgeBase.id}`));
103
+ }
104
+ catch (error) {
105
+ spinner.fail(`Failed to create KB: ${error.message}`);
106
+ }
107
+ });
108
+ kb
109
+ .command('doc-add <kbId> <file>')
110
+ .description('Upload and add a document to a knowledge base')
111
+ .action(async (kbId, file) => {
112
+ const fs = await import('fs');
113
+ const path = await import('path');
114
+ const FormData = (await import('form-data')).default;
115
+ const filePath = path.resolve(file);
116
+ if (!fs.existsSync(filePath)) {
117
+ console.error(chalk.red(`File not found: ${file}`));
118
+ return;
119
+ }
120
+ const spinner = ora('Uploading and adding document...').start();
121
+ try {
122
+ // 1. Upload file
123
+ const formData = new FormData();
124
+ formData.append('file', fs.createReadStream(filePath));
125
+ const uploadResponse = await api.post('/api/uploads', formData, {
126
+ headers: {
127
+ ...formData.getHeaders(),
128
+ },
129
+ });
130
+ const uploadData = uploadResponse.data;
131
+ spinner.text = 'Adding document to KB...';
132
+ // 2. Add to KB via GraphQL
133
+ const mutation = `
134
+ mutation($kbId: ID!, $docs: [CreateKnowledgeDocumentInput!]!) {
135
+ addKnowledgeDocuments(knowledgeBaseId: $kbId, documents: $docs) {
136
+ id
137
+ fileName
138
+ }
139
+ }
140
+ `;
141
+ await graphqlRequest(mutation, {
142
+ kbId,
143
+ docs: [{
144
+ fileName: uploadData.name,
145
+ fileType: uploadData.mimeType,
146
+ storagePath: uploadData.url, // Using URL as storagePath as it's what the server returns
147
+ }]
148
+ });
149
+ spinner.succeed(chalk.green(`Document "${uploadData.name}" added to KB ${kbId}`));
150
+ }
151
+ catch (error) {
152
+ spinner.fail(`Failed: ${error.message}`);
153
+ }
154
+ });
155
+ }
@@ -0,0 +1,87 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { graphqlRequest } from '../utils/graphql.js';
4
+ import ora from 'ora';
5
+ export function registerKeyCommands(program) {
6
+ const key = program.command('key').description('API Key management');
7
+ key
8
+ .command('list')
9
+ .description('List your API keys')
10
+ .action(async () => {
11
+ const spinner = ora('Fetching API keys...').start();
12
+ try {
13
+ const query = `
14
+ query {
15
+ apiKeys {
16
+ id
17
+ name
18
+ provider
19
+ keyValue
20
+ isActive
21
+ lastUsedAt
22
+ }
23
+ }
24
+ `;
25
+ const data = await graphqlRequest(query);
26
+ const keys = data.apiKeys;
27
+ spinner.stop();
28
+ if (keys.length === 0) {
29
+ console.log(chalk.yellow('No API keys found.'));
30
+ return;
31
+ }
32
+ const table = new Table({
33
+ head: [chalk.cyan('ID'), chalk.cyan('Name'), chalk.cyan('Provider'), chalk.cyan('Status'), chalk.cyan('Last Used')],
34
+ colWidths: [36, 20, 15, 10, 25]
35
+ });
36
+ keys.forEach((k) => {
37
+ const status = k.isActive ? chalk.green('Active') : chalk.red('Inactive');
38
+ table.push([k.id, k.name, k.provider, status, k.lastUsedAt || 'Never']);
39
+ });
40
+ console.log(table.toString());
41
+ }
42
+ catch (error) {
43
+ spinner.fail(`Failed to fetch keys: ${error.message}`);
44
+ }
45
+ });
46
+ key
47
+ .command('add <name> <provider> <value>')
48
+ .description('Add a new API key (Providers: openai, anthropic, google, groq, perplexity, xai, openrouter)')
49
+ .action(async (name, provider, value) => {
50
+ const spinner = ora('Adding API key...').start();
51
+ try {
52
+ const mutation = `
53
+ mutation($input: CreateApiKeyInput!) {
54
+ createApiKey(input: $input) {
55
+ id
56
+ name
57
+ }
58
+ }
59
+ `;
60
+ const data = await graphqlRequest(mutation, {
61
+ input: { name, provider, keyValue: value }
62
+ });
63
+ spinner.succeed(chalk.green(`API key "${data.createApiKey.name}" added successfully.`));
64
+ }
65
+ catch (error) {
66
+ spinner.fail(`Failed to add key: ${error.message}`);
67
+ }
68
+ });
69
+ key
70
+ .command('delete <id>')
71
+ .description('Delete an API key')
72
+ .action(async (id) => {
73
+ const spinner = ora('Deleting API key...').start();
74
+ try {
75
+ const mutation = `
76
+ mutation($id: ID!) {
77
+ deleteApiKey(id: $id)
78
+ }
79
+ `;
80
+ await graphqlRequest(mutation, { id });
81
+ spinner.succeed(chalk.green('API key deleted.'));
82
+ }
83
+ catch (error) {
84
+ spinner.fail(`Failed to delete key: ${error.message}`);
85
+ }
86
+ });
87
+ }
@@ -0,0 +1,56 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { graphqlRequest } from '../utils/graphql.js';
4
+ import ora from 'ora';
5
+ export function registerMcpCommands(program) {
6
+ const mcp = program.command('mcp').description('Model Context Protocol (MCP) management');
7
+ mcp
8
+ .command('agent <agentId>')
9
+ .description('List MCP servers configured for an agent')
10
+ .action(async (agentId) => {
11
+ const globalOptions = program.opts();
12
+ const spinner = !globalOptions.json ? ora('Checking MCP status...').start() : null;
13
+ try {
14
+ const query = `
15
+ query($id: ID!) {
16
+ agentWithFlow(id: $id) {
17
+ flowNodes {
18
+ nodeId
19
+ nodeType
20
+ data
21
+ }
22
+ }
23
+ }
24
+ `;
25
+ const data = await graphqlRequest(query, { id: agentId });
26
+ const mcpNodes = data.agentWithFlow.flowNodes.filter((n) => n.nodeType === 'custom_mcp');
27
+ if (spinner)
28
+ spinner.stop();
29
+ if (globalOptions.json) {
30
+ console.log(JSON.stringify(mcpNodes, null, 2));
31
+ return;
32
+ }
33
+ if (mcpNodes.length === 0) {
34
+ console.log(chalk.yellow('No MCP nodes found for this agent.'));
35
+ return;
36
+ }
37
+ const table = new Table({
38
+ head: [chalk.cyan('Node ID'), chalk.cyan('Type'), chalk.cyan('Preset/Label'), chalk.cyan('Status')],
39
+ colWidths: [36, 20, 20, 15]
40
+ });
41
+ mcpNodes.forEach((n) => {
42
+ const nodeData = n.data || {};
43
+ const status = nodeData.connectionStatus || 'unknown';
44
+ const label = nodeData.title || n.nodeType;
45
+ table.push([n.nodeId, n.nodeType, label, status === 'connected' ? chalk.green(status) : chalk.red(status)]);
46
+ });
47
+ console.log(table.toString());
48
+ }
49
+ catch (error) {
50
+ if (spinner)
51
+ spinner.fail(`Failed: ${error.message}`);
52
+ else
53
+ console.error(JSON.stringify({ error: error.message }));
54
+ }
55
+ });
56
+ }
@@ -0,0 +1,103 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { graphqlRequest } from '../utils/graphql.js';
4
+ import ora from 'ora';
5
+ export function registerMemberCommands(program) {
6
+ const member = program.command('member').description('Member management');
7
+ member
8
+ .command('list <workspaceId>')
9
+ .description('List all members of a workspace')
10
+ .action(async (workspaceId) => {
11
+ const globalOptions = program.opts();
12
+ const spinner = !globalOptions.json ? ora('Fetching members...').start() : null;
13
+ try {
14
+ const query = `
15
+ query($workspaceId: ID!) {
16
+ workspaceMembers(workspaceId: $workspaceId) {
17
+ id
18
+ role
19
+ joinedAt
20
+ user {
21
+ id
22
+ name
23
+ email
24
+ }
25
+ }
26
+ }
27
+ `;
28
+ const data = await graphqlRequest(query, { workspaceId });
29
+ const members = data.workspaceMembers;
30
+ if (spinner)
31
+ spinner.stop();
32
+ if (globalOptions.json) {
33
+ console.log(JSON.stringify(members, null, 2));
34
+ return;
35
+ }
36
+ if (members.length === 0) {
37
+ console.log(chalk.yellow('No members found.'));
38
+ return;
39
+ }
40
+ const table = new Table({
41
+ head: [chalk.cyan('Member ID'), chalk.cyan('Name'), chalk.cyan('Email'), chalk.cyan('Role'), chalk.cyan('Joined')],
42
+ colWidths: [36, 20, 30, 10, 25]
43
+ });
44
+ members.forEach((m) => {
45
+ table.push([m.user.id, m.user.name || 'N/A', m.user.email, m.role, m.joinedAt]);
46
+ });
47
+ console.log(table.toString());
48
+ }
49
+ catch (error) {
50
+ if (spinner)
51
+ spinner.fail(`Failed to fetch members: ${error.message}`);
52
+ else
53
+ console.error(JSON.stringify({ error: error.message }));
54
+ }
55
+ });
56
+ member
57
+ .command('update <workspaceId> <memberId>')
58
+ .description('Update a member role (OWNER, EDITOR, VIEWER)')
59
+ .option('-r, --role <role>', 'New role')
60
+ .action(async (workspaceId, memberId, options) => {
61
+ if (!options.role) {
62
+ console.error(chalk.red('Role is required. Use -r <role>'));
63
+ return;
64
+ }
65
+ const spinner = ora('Updating member role...').start();
66
+ try {
67
+ const mutation = `
68
+ mutation($memberId: ID!, $input: UpdateWorkspaceMemberRoleInput!) {
69
+ updateWorkspaceMemberRole(memberId: $memberId, input: $input) {
70
+ id
71
+ role
72
+ }
73
+ }
74
+ `;
75
+ await graphqlRequest(mutation, {
76
+ memberId,
77
+ input: { role: options.role.toUpperCase() }
78
+ });
79
+ spinner.succeed(chalk.green(`Member role updated to ${options.role.toUpperCase()}.`));
80
+ }
81
+ catch (error) {
82
+ spinner.fail(`Failed to update member: ${error.message}`);
83
+ }
84
+ });
85
+ member
86
+ .command('remove <workspaceId> <memberId>')
87
+ .description('Remove a member from a workspace')
88
+ .action(async (workspaceId, memberId) => {
89
+ const spinner = ora('Removing member...').start();
90
+ try {
91
+ const mutation = `
92
+ mutation($memberId: ID!) {
93
+ removeWorkspaceMember(memberId: $memberId)
94
+ }
95
+ `;
96
+ await graphqlRequest(mutation, { memberId });
97
+ spinner.succeed(chalk.green('Member removed from workspace.'));
98
+ }
99
+ catch (error) {
100
+ spinner.fail(`Failed to remove member: ${error.message}`);
101
+ }
102
+ });
103
+ }
@@ -0,0 +1,65 @@
1
+ import chalk from 'chalk';
2
+ import config from '../utils/config.js';
3
+ import Table from 'cli-table3';
4
+ export function registerProfileCommands(program) {
5
+ const profile = program.command('profile').description('Environment profiles management');
6
+ profile
7
+ .command('list')
8
+ .description('List all profiles')
9
+ .action(() => {
10
+ const profiles = config.get('profiles') || {};
11
+ const current = config.get('currentProfile');
12
+ if (Object.keys(profiles).length === 0) {
13
+ console.log(chalk.yellow('No profiles found. Use hsafa profile add <name> <url>'));
14
+ return;
15
+ }
16
+ const table = new Table({
17
+ head: [chalk.cyan('Active'), chalk.cyan('Name'), chalk.cyan('Server URL')],
18
+ colWidths: [10, 20, 40]
19
+ });
20
+ Object.entries(profiles).forEach(([name, data]) => {
21
+ table.push([name === current ? chalk.green(' * ') : '', name, data.serverUrl]);
22
+ });
23
+ console.log(table.toString());
24
+ });
25
+ profile
26
+ .command('add <name> <url>')
27
+ .description('Add a new profile')
28
+ .action((name, url) => {
29
+ const profiles = config.get('profiles') || {};
30
+ profiles[name] = { serverUrl: url };
31
+ config.set('profiles', profiles);
32
+ console.log(chalk.green(`Profile "${name}" added.`));
33
+ });
34
+ profile
35
+ .command('use <name>')
36
+ .description('Switch to a profile')
37
+ .action((name) => {
38
+ const profiles = config.get('profiles') || {};
39
+ if (!profiles[name]) {
40
+ console.error(chalk.red(`Profile "${name}" not found.`));
41
+ return;
42
+ }
43
+ const current = profiles[name];
44
+ config.set('currentProfile', name);
45
+ config.set('serverUrl', current.serverUrl);
46
+ config.set('token', current.token || '');
47
+ console.log(chalk.green(`Switched to profile "${name}" (${current.serverUrl})`));
48
+ });
49
+ profile
50
+ .command('delete <name>')
51
+ .description('Delete a profile')
52
+ .action((name) => {
53
+ const profiles = config.get('profiles') || {};
54
+ if (!profiles[name]) {
55
+ console.error(chalk.red(`Profile "${name}" not found.`));
56
+ return;
57
+ }
58
+ delete profiles[name];
59
+ config.set('profiles', profiles);
60
+ if (config.get('currentProfile') === name) {
61
+ config.delete('currentProfile');
62
+ }
63
+ console.log(chalk.green(`Profile "${name}" deleted.`));
64
+ });
65
+ }
@@ -0,0 +1,90 @@
1
+ import chalk from 'chalk';
2
+ import { graphqlRequest } from '../utils/graphql.js';
3
+ import ora from 'ora';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ export function registerProjectCommands(program) {
9
+ const project = program.command('project').description('Project management');
10
+ project
11
+ .command('init <name>')
12
+ .description('Initialize a new project with a template')
13
+ .option('-w, --workspace <id>', 'Workspace ID (if not provided, will use the first one)')
14
+ .option('-t, --template <name>', 'Template name (basic-assistant, researcher)', 'basic-assistant')
15
+ .action(async (name, options) => {
16
+ const spinner = ora('Initializing project...').start();
17
+ try {
18
+ // 1. Get Workspace ID
19
+ let workspaceId = options.workspace;
20
+ if (!workspaceId) {
21
+ const workspacesQuery = `query { workspaces { id name } }`;
22
+ const wsData = await graphqlRequest(workspacesQuery);
23
+ if (wsData.workspaces.length === 0) {
24
+ spinner.fail('No workspaces found. Create a workspace first using the UI or GraphQL.');
25
+ return;
26
+ }
27
+ workspaceId = wsData.workspaces[0].id;
28
+ spinner.info(`Using workspace: ${wsData.workspaces[0].name} (${workspaceId})`);
29
+ spinner.start('Creating project...');
30
+ }
31
+ // 2. Create Project
32
+ const createProjectMutation = `
33
+ mutation($input: CreateProjectInput!) {
34
+ createProject(input: $input) {
35
+ id
36
+ name
37
+ }
38
+ }
39
+ `;
40
+ // Note: The GraphQL schema for createProject might vary, let's assume it takes workspaceId
41
+ // Looking at current server code, projects are often created within a workspace scope.
42
+ // However, I'll use the createAgent mutation which we know works well.
43
+ // Let's create an agent directly in the workspace as per current hsafa server structure
44
+ const createAgentMutation = `
45
+ mutation($workspaceId: ID!, $input: CreateAgentInput!) {
46
+ createAgent(workspaceId: $workspaceId, input: $input) {
47
+ id
48
+ name
49
+ }
50
+ }
51
+ `;
52
+ const agentData = await graphqlRequest(createAgentMutation, {
53
+ workspaceId,
54
+ input: {
55
+ name,
56
+ description: `Project initialized from ${options.template} template`,
57
+ type: 'AI Agent'
58
+ }
59
+ });
60
+ const agentId = agentData.createAgent.id;
61
+ spinner.text = `Applying template ${options.template}...`;
62
+ // 3. Load Template
63
+ const templatePath = path.join(__dirname, '..', 'templates', `${options.template}.json`);
64
+ if (!fs.existsSync(templatePath)) {
65
+ spinner.fail(`Template ${options.template} not found.`);
66
+ return;
67
+ }
68
+ const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
69
+ // 4. Apply Template Flow
70
+ const saveFlowMutation = `
71
+ mutation($agentId: ID!, $input: SaveAgentFlowInput!) {
72
+ saveAgentFlow(agentId: $agentId, input: $input)
73
+ }
74
+ `;
75
+ await graphqlRequest(saveFlowMutation, {
76
+ agentId,
77
+ input: {
78
+ nodes: template.nodes,
79
+ edges: template.edges
80
+ }
81
+ });
82
+ spinner.succeed(chalk.green(`Project "${name}" initialized successfully!`));
83
+ console.log(`\nAgent ID: ${chalk.cyan(agentId)}`);
84
+ console.log(`Run chat: ${chalk.bold(`hsafa agent chat ${agentId}`)}`);
85
+ }
86
+ catch (error) {
87
+ spinner.fail(`Initialization failed: ${error.message}`);
88
+ }
89
+ });
90
+ }
@@ -0,0 +1,27 @@
1
+ import chalk from 'chalk';
2
+ import api from '../utils/api.js';
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ export function registerSystemCommands(program) {
6
+ program
7
+ .command('status')
8
+ .description('Check HSAFA system status')
9
+ .action(async () => {
10
+ const spinner = ora('Checking system status...').start();
11
+ try {
12
+ const response = await api.get('/api/health');
13
+ const data = response.data;
14
+ spinner.stop();
15
+ console.log(chalk.bold('\nHSAFA System Status:'));
16
+ const table = new Table();
17
+ table.push({ 'Status': data.status === 'healthy' ? chalk.green(data.status) : chalk.red(data.status) }, { 'Timestamp': data.timestamp }, { 'Database': data.database === 'connected' ? chalk.green(data.database) : chalk.red(data.database) }, { 'Server': chalk.green(data.server) });
18
+ if (data.error) {
19
+ table.push({ 'Error': chalk.red(data.error) });
20
+ }
21
+ console.log(table.toString());
22
+ }
23
+ catch (error) {
24
+ spinner.fail(`Failed to check status: ${error.message}`);
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,48 @@
1
+ import chalk from 'chalk';
2
+ import { graphqlRequest } from '../utils/graphql.js';
3
+ import ora from 'ora';
4
+ import Table from 'cli-table3';
5
+ export function registerUserCommands(program) {
6
+ program
7
+ .command('me')
8
+ .description('Show current user profile')
9
+ .action(async () => {
10
+ const globalOptions = program.opts();
11
+ const spinner = !globalOptions.json ? ora('Fetching profile...').start() : null;
12
+ try {
13
+ const query = `
14
+ query {
15
+ me {
16
+ id
17
+ email
18
+ name
19
+ avatarUrl
20
+ createdAt
21
+ }
22
+ }
23
+ `;
24
+ const data = await graphqlRequest(query);
25
+ const user = data.me;
26
+ if (spinner)
27
+ spinner.stop();
28
+ if (globalOptions.json) {
29
+ console.log(JSON.stringify(user, null, 2));
30
+ return;
31
+ }
32
+ if (!user) {
33
+ console.log(chalk.red('Not logged in or user not found.'));
34
+ return;
35
+ }
36
+ const table = new Table();
37
+ table.push({ 'ID': user.id }, { 'Name': user.name || 'N/A' }, { 'Email': user.email }, { 'Created At': user.createdAt });
38
+ console.log(chalk.bold('\nYour Profile:'));
39
+ console.log(table.toString());
40
+ }
41
+ catch (error) {
42
+ if (spinner)
43
+ spinner.fail(`Failed to fetch profile: ${error.message}`);
44
+ else
45
+ console.error(JSON.stringify({ error: error.message }));
46
+ }
47
+ });
48
+ }