@hsafa/cli 1.0.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 +72 -0
- package/dist/commands/agent.js +853 -0
- package/dist/commands/auth.js +168 -0
- package/dist/commands/chat.js +233 -0
- package/dist/commands/doc.js +37 -0
- package/dist/commands/gql.js +106 -0
- package/dist/commands/invite.js +147 -0
- package/dist/commands/kb.js +155 -0
- package/dist/commands/key.js +87 -0
- package/dist/commands/mcp.js +56 -0
- package/dist/commands/member.js +103 -0
- package/dist/commands/profile.js +65 -0
- package/dist/commands/project.js +90 -0
- package/dist/commands/system.js +27 -0
- package/dist/commands/user.js +48 -0
- package/dist/commands/workspace.js +77 -0
- package/dist/index.js +56 -0
- package/dist/templates/basic-assistant.json +57 -0
- package/dist/templates/researcher.json +58 -0
- package/dist/utils/api.js +13 -0
- package/dist/utils/config.js +22 -0
- package/dist/utils/graphql.js +17 -0
- package/package.json +43 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import config from '../utils/config.js';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import http from 'http';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
import { URL } from 'url';
|
|
9
|
+
export function registerAuthCommands(program) {
|
|
10
|
+
const auth = program.command('auth').description('Authentication management');
|
|
11
|
+
auth
|
|
12
|
+
.command('login')
|
|
13
|
+
.description('Login to HSAFA')
|
|
14
|
+
.option('--manual', 'Login manually with email and password')
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
if (options.manual) {
|
|
17
|
+
await loginManual();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
await loginBrowser();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
async function loginBrowser() {
|
|
24
|
+
const serverUrl = config.get('serverUrl');
|
|
25
|
+
console.log(chalk.cyan('Authentication requested.'));
|
|
26
|
+
console.log(`We will open your browser to log you in to: ${serverUrl}`);
|
|
27
|
+
const { confirm } = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: 'confirm',
|
|
30
|
+
name: 'confirm',
|
|
31
|
+
message: 'Press Enter to open browser...',
|
|
32
|
+
default: true,
|
|
33
|
+
},
|
|
34
|
+
]);
|
|
35
|
+
if (!confirm) {
|
|
36
|
+
console.log(chalk.yellow('Login cancelled.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const spinner = ora('Waiting for authentication...').start();
|
|
40
|
+
// Start local server to receive the token
|
|
41
|
+
const server = http.createServer(async (req, res) => {
|
|
42
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
43
|
+
const token = url.searchParams.get('token');
|
|
44
|
+
if (token) {
|
|
45
|
+
// Save token
|
|
46
|
+
saveToken(token);
|
|
47
|
+
// Respond to browser
|
|
48
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
49
|
+
res.end(`
|
|
50
|
+
<html>
|
|
51
|
+
<body style="background:#111; color:#fff; font-family:sans-serif; text-align:center; padding-top:50px;">
|
|
52
|
+
<h1>Login Successful!</h1>
|
|
53
|
+
<p>You can close this tab and return to the CLI.</p>
|
|
54
|
+
<script>window.close();</script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
`);
|
|
58
|
+
spinner.succeed(chalk.green('Login successful!'));
|
|
59
|
+
server.close();
|
|
60
|
+
process.exit(0); // Exit successfully
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
64
|
+
res.end('No token provided.');
|
|
65
|
+
spinner.fail(chalk.red('Login failed: No token received.'));
|
|
66
|
+
server.close();
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// Listen on a random port
|
|
71
|
+
server.listen(0, async () => {
|
|
72
|
+
const address = server.address();
|
|
73
|
+
const port = address.port;
|
|
74
|
+
const callbackUrl = `http://localhost:${port}`;
|
|
75
|
+
// Open the browser
|
|
76
|
+
// Point to the server-side CLI login page which will redirect back to us with the token
|
|
77
|
+
// We assume the server has a route /cli-login that handles the auth check and redirects back to callbackUrl
|
|
78
|
+
// The server page logic:
|
|
79
|
+
// 1. Check if user is logged in (session).
|
|
80
|
+
// 2. If not, redirect to /auth/signin?callbackUrl=/cli-login?callback=callbackUrl
|
|
81
|
+
// 3. If logged in, get session token.
|
|
82
|
+
// 4. Redirect to callbackUrl?token=sessionToken
|
|
83
|
+
const loginUrl = `${serverUrl}/cli-login?callback=${encodeURIComponent(callbackUrl)}`;
|
|
84
|
+
try {
|
|
85
|
+
await open(loginUrl);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
spinner.fail(chalk.red('Failed to open browser. Please open this URL manually:'));
|
|
89
|
+
console.log(loginUrl);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async function loginManual() {
|
|
94
|
+
const answers = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'input',
|
|
97
|
+
name: 'email',
|
|
98
|
+
message: 'Enter your email:',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'password',
|
|
102
|
+
name: 'password',
|
|
103
|
+
message: 'Enter your password:',
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
const spinner = ora('Logging in...').start();
|
|
107
|
+
try {
|
|
108
|
+
const serverUrl = config.get('serverUrl');
|
|
109
|
+
const response = await axios.post(`${serverUrl}/api/auth/signin`, {
|
|
110
|
+
email: answers.email,
|
|
111
|
+
password: answers.password,
|
|
112
|
+
}, {
|
|
113
|
+
withCredentials: true,
|
|
114
|
+
});
|
|
115
|
+
// Extract cookie
|
|
116
|
+
const setCookie = response.headers['set-cookie'];
|
|
117
|
+
if (setCookie) {
|
|
118
|
+
const sessionTokenMatch = setCookie.find(c => c.includes('next-auth.session-token'));
|
|
119
|
+
if (sessionTokenMatch) {
|
|
120
|
+
const token = sessionTokenMatch.split(';')[0].split('=')[1];
|
|
121
|
+
saveToken(token);
|
|
122
|
+
spinner.succeed(chalk.green('Login successful!'));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
spinner.fail(chalk.red('Login failed: Could not find session token in response.'));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
spinner.fail(chalk.red('Login failed: No cookies returned from server.'));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
const errorMsg = error.response?.data?.error || error.message;
|
|
134
|
+
spinner.fail(chalk.red(`Login failed: ${errorMsg}`));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function saveToken(token) {
|
|
138
|
+
config.set('token', token);
|
|
139
|
+
// Save to current profile as well
|
|
140
|
+
const currentProfile = config.get('currentProfile');
|
|
141
|
+
if (currentProfile) {
|
|
142
|
+
const profiles = config.get('profiles') || {};
|
|
143
|
+
if (profiles[currentProfile]) {
|
|
144
|
+
profiles[currentProfile].token = token;
|
|
145
|
+
config.set('profiles', profiles);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
auth
|
|
150
|
+
.command('logout')
|
|
151
|
+
.description('Logout from HSAFA')
|
|
152
|
+
.action(() => {
|
|
153
|
+
config.delete('token');
|
|
154
|
+
console.log(chalk.green('Logged out successfully.'));
|
|
155
|
+
});
|
|
156
|
+
auth
|
|
157
|
+
.command('status')
|
|
158
|
+
.description('Check authentication status')
|
|
159
|
+
.action(() => {
|
|
160
|
+
const token = config.get('token');
|
|
161
|
+
if (token) {
|
|
162
|
+
console.log(chalk.green('You are logged in.'));
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
console.log(chalk.yellow('You are not logged in.'));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
import inquirer from 'inquirer';
|
|
7
|
+
export function registerChatCommands(program) {
|
|
8
|
+
const chat = program.command('chat').description('Chat session management');
|
|
9
|
+
chat
|
|
10
|
+
.command('list')
|
|
11
|
+
.description('List your previous chat sessions')
|
|
12
|
+
.option('-a, --agent <id>', 'Filter by agent ID')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
const globalOptions = program.opts();
|
|
15
|
+
const spinner = !globalOptions.json ? ora('Fetching chats...').start() : null;
|
|
16
|
+
try {
|
|
17
|
+
const query = `
|
|
18
|
+
query($agentId: ID) {
|
|
19
|
+
agentTestChats(agentId: $agentId) {
|
|
20
|
+
id
|
|
21
|
+
title
|
|
22
|
+
agentId
|
|
23
|
+
createdAt
|
|
24
|
+
updatedAt
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
const data = await graphqlRequest(query, { agentId: options.agent });
|
|
29
|
+
const chats = data.agentTestChats;
|
|
30
|
+
if (spinner)
|
|
31
|
+
spinner.stop();
|
|
32
|
+
if (globalOptions.json) {
|
|
33
|
+
console.log(JSON.stringify(chats, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (chats.length === 0) {
|
|
37
|
+
console.log(chalk.yellow('No chat sessions found.'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const table = new Table({
|
|
41
|
+
head: [chalk.cyan('ID'), chalk.cyan('Title'), chalk.cyan('Agent ID'), chalk.cyan('Updated')],
|
|
42
|
+
colWidths: [36, 25, 36, 25]
|
|
43
|
+
});
|
|
44
|
+
chats.forEach((c) => {
|
|
45
|
+
table.push([c.id, c.title || 'Untitled', c.agentId, c.updatedAt]);
|
|
46
|
+
});
|
|
47
|
+
console.log(table.toString());
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (spinner)
|
|
51
|
+
spinner.fail(`Failed to fetch chats: ${error.message}`);
|
|
52
|
+
else
|
|
53
|
+
console.error(JSON.stringify({ error: error.message }));
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
chat
|
|
57
|
+
.command('resume <id>')
|
|
58
|
+
.description('Resume a previous chat session')
|
|
59
|
+
.action(async (id) => {
|
|
60
|
+
const spinner = ora('Fetching chat history...').start();
|
|
61
|
+
try {
|
|
62
|
+
const query = `
|
|
63
|
+
query($id: ID!) {
|
|
64
|
+
agentTestChat(id: $id) {
|
|
65
|
+
id
|
|
66
|
+
agentId
|
|
67
|
+
title
|
|
68
|
+
messages {
|
|
69
|
+
role: sender
|
|
70
|
+
content
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
const data = await graphqlRequest(query, { id });
|
|
76
|
+
const chatData = data.agentTestChat;
|
|
77
|
+
if (!chatData) {
|
|
78
|
+
spinner.fail('Chat session not found.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
spinner.succeed(`Resuming chat: ${chatData.title || 'Untitled'}`);
|
|
82
|
+
// Convert SENDER enum to role
|
|
83
|
+
const history = chatData.messages.map((m) => ({
|
|
84
|
+
role: m.role.toLowerCase() === 'agent' ? 'assistant' : 'user',
|
|
85
|
+
content: m.content
|
|
86
|
+
}));
|
|
87
|
+
await startChatSession(chatData.agentId, chatData.id, history);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
spinner.fail(`Failed to resume chat: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
chat
|
|
94
|
+
.command('delete <id>')
|
|
95
|
+
.description('Delete a chat session')
|
|
96
|
+
.action(async (id) => {
|
|
97
|
+
const spinner = ora('Deleting chat session...').start();
|
|
98
|
+
try {
|
|
99
|
+
const mutation = `
|
|
100
|
+
mutation($id: ID!) {
|
|
101
|
+
deleteAgentTestChat(id: $id)
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
await graphqlRequest(mutation, { id });
|
|
105
|
+
spinner.succeed(chalk.green('Chat session deleted.'));
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
spinner.fail(`Failed to delete chat: ${error.message}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export async function startChatSession(agentId, existingChatId, history = []) {
|
|
113
|
+
let chatId = existingChatId;
|
|
114
|
+
const messages = [...history];
|
|
115
|
+
if (!chatId) {
|
|
116
|
+
const spinner = ora('Creating new chat session...').start();
|
|
117
|
+
try {
|
|
118
|
+
const mutation = `
|
|
119
|
+
mutation($input: CreateAgentTestChatInput!) {
|
|
120
|
+
createAgentTestChat(input: $input) {
|
|
121
|
+
id
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
const data = await graphqlRequest(mutation, {
|
|
126
|
+
input: { agentId, title: `CLI Chat ${new Date().toLocaleString()}` }
|
|
127
|
+
});
|
|
128
|
+
chatId = data.createAgentTestChat.id;
|
|
129
|
+
spinner.succeed(chalk.gray(`New session created: ${chatId}`));
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
spinner.fail(`Failed to create session: ${error.message}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
console.log(chalk.blue(`Chatting with agent ${agentId}`));
|
|
137
|
+
if (messages.length > 0) {
|
|
138
|
+
console.log(chalk.gray(`Loaded ${messages.length} messages from history.`));
|
|
139
|
+
messages.forEach(m => {
|
|
140
|
+
const label = m.role === 'user' ? chalk.green('You:') : chalk.cyan('Agent:');
|
|
141
|
+
console.log(`${label} ${m.content}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
console.log(chalk.gray('Type "exit" or "quit" to stop.\n'));
|
|
145
|
+
while (true) {
|
|
146
|
+
const { prompt } = await inquirer.prompt([
|
|
147
|
+
{
|
|
148
|
+
type: 'input',
|
|
149
|
+
name: 'prompt',
|
|
150
|
+
message: chalk.green('You:'),
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
153
|
+
if (!prompt || prompt.toLowerCase() === 'exit' || prompt.toLowerCase() === 'quit') {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
const userMessage = { role: 'user', content: prompt };
|
|
157
|
+
messages.push(userMessage);
|
|
158
|
+
// Persist user message
|
|
159
|
+
try {
|
|
160
|
+
const mutation = `
|
|
161
|
+
mutation($input: AppendAgentTestMessageInput!) {
|
|
162
|
+
appendAgentTestMessage(input: $input) { id }
|
|
163
|
+
}
|
|
164
|
+
`;
|
|
165
|
+
await graphqlRequest(mutation, {
|
|
166
|
+
input: {
|
|
167
|
+
chatId,
|
|
168
|
+
sender: 'USER',
|
|
169
|
+
content: prompt
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
console.error(chalk.yellow('Warning: Could not persist message to history.'));
|
|
175
|
+
}
|
|
176
|
+
process.stdout.write(chalk.cyan('Agent: '));
|
|
177
|
+
try {
|
|
178
|
+
const response = await api.post(`/api/run/${agentId}`, {
|
|
179
|
+
messages: messages.map(m => ({
|
|
180
|
+
role: m.role,
|
|
181
|
+
content: m.content
|
|
182
|
+
})),
|
|
183
|
+
}, {
|
|
184
|
+
responseType: 'stream',
|
|
185
|
+
});
|
|
186
|
+
const stream = response.data;
|
|
187
|
+
let assistantContent = '';
|
|
188
|
+
await new Promise((resolve, reject) => {
|
|
189
|
+
let buffer = '';
|
|
190
|
+
stream.on('data', (chunk) => {
|
|
191
|
+
buffer += chunk.toString();
|
|
192
|
+
const lines = buffer.split('\n');
|
|
193
|
+
buffer = lines.pop() || '';
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
if (!line.trim())
|
|
196
|
+
continue;
|
|
197
|
+
try {
|
|
198
|
+
const data = JSON.parse(line);
|
|
199
|
+
if (data.type === 'text-delta' || data.type === 'text') {
|
|
200
|
+
const delta = data.textDelta || data.text;
|
|
201
|
+
process.stdout.write(delta);
|
|
202
|
+
assistantContent += delta;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (e) { }
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
stream.on('end', () => {
|
|
209
|
+
process.stdout.write('\n\n');
|
|
210
|
+
const assistantMessage = { role: 'assistant', content: assistantContent };
|
|
211
|
+
messages.push(assistantMessage);
|
|
212
|
+
// Persist assistant message
|
|
213
|
+
graphqlRequest(`
|
|
214
|
+
mutation($input: AppendAgentTestMessageInput!) {
|
|
215
|
+
appendAgentTestMessage(input: $input) { id }
|
|
216
|
+
}
|
|
217
|
+
`, {
|
|
218
|
+
input: {
|
|
219
|
+
chatId,
|
|
220
|
+
sender: 'AGENT',
|
|
221
|
+
content: assistantContent
|
|
222
|
+
}
|
|
223
|
+
}).catch(() => { });
|
|
224
|
+
resolve();
|
|
225
|
+
});
|
|
226
|
+
stream.on('error', reject);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import api from '../utils/api.js';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import FormData from 'form-data';
|
|
7
|
+
export function registerDocCommands(program) {
|
|
8
|
+
const doc = program.command('doc').description('Document management');
|
|
9
|
+
doc
|
|
10
|
+
.command('upload <file>')
|
|
11
|
+
.description('Upload a document')
|
|
12
|
+
.action(async (file) => {
|
|
13
|
+
const filePath = path.resolve(file);
|
|
14
|
+
if (!fs.existsSync(filePath)) {
|
|
15
|
+
console.error(chalk.red(`File not found: ${file}`));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const spinner = ora('Uploading document...').start();
|
|
19
|
+
try {
|
|
20
|
+
const formData = new FormData();
|
|
21
|
+
formData.append('file', fs.createReadStream(filePath));
|
|
22
|
+
const response = await api.post('/api/uploads', formData, {
|
|
23
|
+
headers: {
|
|
24
|
+
...formData.getHeaders(),
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
spinner.succeed(chalk.green('Document uploaded successfully!'));
|
|
28
|
+
console.log(chalk.cyan('File details:'));
|
|
29
|
+
console.log(`ID: ${response.data.id}`);
|
|
30
|
+
console.log(`URL: ${response.data.url}`);
|
|
31
|
+
console.log(`Storage: ${response.data.storage}`);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
spinner.fail(`Upload failed: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { graphqlRequest } from '../utils/graphql.js';
|
|
5
|
+
async function readStdin() {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
for await (const chunk of process.stdin) {
|
|
8
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
9
|
+
}
|
|
10
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
11
|
+
}
|
|
12
|
+
export function registerGqlCommands(program) {
|
|
13
|
+
const gql = program.command('gql').description('Run arbitrary GraphQL queries/mutations');
|
|
14
|
+
gql
|
|
15
|
+
.command('run')
|
|
16
|
+
.description('Execute a GraphQL operation')
|
|
17
|
+
.option('--query <graphql>', 'GraphQL query/mutation string')
|
|
18
|
+
.option('--query-file <path>', 'Read GraphQL query/mutation from file')
|
|
19
|
+
.option('--query-stdin', 'Read GraphQL query/mutation from stdin')
|
|
20
|
+
.option('--variables <json>', 'Variables JSON object')
|
|
21
|
+
.option('--variables-file <path>', 'Read variables JSON from file')
|
|
22
|
+
.option('--variables-stdin', 'Read variables JSON from stdin')
|
|
23
|
+
.option('--operation-name <name>', 'GraphQL operationName (optional)')
|
|
24
|
+
.option('--no-pretty', 'Disable pretty JSON formatting')
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
const globalOptions = program.opts();
|
|
27
|
+
const spinner = !globalOptions.json ? ora('Running GraphQL...').start() : null;
|
|
28
|
+
try {
|
|
29
|
+
let query = options.query;
|
|
30
|
+
if (!query && options.queryFile) {
|
|
31
|
+
query = fs.readFileSync(options.queryFile, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
if (!query && options.queryStdin) {
|
|
34
|
+
query = await readStdin();
|
|
35
|
+
}
|
|
36
|
+
if (!query) {
|
|
37
|
+
throw new Error('No query provided. Use --query, --query-file, or --query-stdin');
|
|
38
|
+
}
|
|
39
|
+
let variables = {};
|
|
40
|
+
if (options.variables !== undefined) {
|
|
41
|
+
variables = JSON.parse(options.variables);
|
|
42
|
+
}
|
|
43
|
+
else if (options.variablesFile) {
|
|
44
|
+
variables = JSON.parse(fs.readFileSync(options.variablesFile, 'utf8'));
|
|
45
|
+
}
|
|
46
|
+
else if (options.variablesStdin) {
|
|
47
|
+
const text = await readStdin();
|
|
48
|
+
variables = JSON.parse(text);
|
|
49
|
+
}
|
|
50
|
+
const data = await graphqlRequest(query, variables, {
|
|
51
|
+
operationName: options.operationName,
|
|
52
|
+
});
|
|
53
|
+
if (spinner)
|
|
54
|
+
spinner.stop();
|
|
55
|
+
const pretty = options.pretty !== false;
|
|
56
|
+
const json = JSON.stringify(data, null, pretty ? 2 : 0);
|
|
57
|
+
console.log(json);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (spinner)
|
|
61
|
+
spinner.fail(`GraphQL failed: ${error.message}`);
|
|
62
|
+
else
|
|
63
|
+
console.error(JSON.stringify({ error: error.message }, null, 2));
|
|
64
|
+
if (!globalOptions.json && !spinner) {
|
|
65
|
+
console.error(chalk.red(error.message));
|
|
66
|
+
}
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
gql
|
|
71
|
+
.command('introspect')
|
|
72
|
+
.description('Fetch the GraphQL schema via introspection')
|
|
73
|
+
.option('--no-pretty', 'Disable pretty JSON formatting')
|
|
74
|
+
.action(async (options) => {
|
|
75
|
+
const globalOptions = program.opts();
|
|
76
|
+
const spinner = !globalOptions.json ? ora('Fetching schema...').start() : null;
|
|
77
|
+
try {
|
|
78
|
+
const query = `
|
|
79
|
+
query IntrospectionQuery {
|
|
80
|
+
__schema {
|
|
81
|
+
queryType { name }
|
|
82
|
+
mutationType { name }
|
|
83
|
+
types {
|
|
84
|
+
kind
|
|
85
|
+
name
|
|
86
|
+
description
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
const data = await graphqlRequest(query, {}, { operationName: 'IntrospectionQuery' });
|
|
92
|
+
if (spinner)
|
|
93
|
+
spinner.stop();
|
|
94
|
+
const pretty = options.pretty !== false;
|
|
95
|
+
const json = JSON.stringify(data, null, pretty ? 2 : 0);
|
|
96
|
+
console.log(json);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (spinner)
|
|
100
|
+
spinner.fail(`Introspection failed: ${error.message}`);
|
|
101
|
+
else
|
|
102
|
+
console.error(JSON.stringify({ error: error.message }, null, 2));
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
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 registerInviteCommands(program) {
|
|
6
|
+
const invite = program.command('invite').description('Invitation management');
|
|
7
|
+
invite
|
|
8
|
+
.command('send <workspaceId> <email>')
|
|
9
|
+
.description('Invite a user to a workspace')
|
|
10
|
+
.option('-r, --role <role>', 'Role (OWNER, EDITOR, VIEWER)', 'VIEWER')
|
|
11
|
+
.action(async (workspaceId, email, options) => {
|
|
12
|
+
const spinner = ora(`Inviting ${email}...`).start();
|
|
13
|
+
try {
|
|
14
|
+
const mutation = `
|
|
15
|
+
mutation($workspaceId: ID!, $input: InviteToWorkspaceInput!) {
|
|
16
|
+
inviteToWorkspace(workspaceId: $workspaceId, input: $input) {
|
|
17
|
+
id
|
|
18
|
+
email
|
|
19
|
+
token
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
const data = await graphqlRequest(mutation, {
|
|
24
|
+
workspaceId,
|
|
25
|
+
input: { email, role: options.role.toUpperCase() }
|
|
26
|
+
});
|
|
27
|
+
spinner.succeed(chalk.green(`Invitation sent to ${email}. Token: ${data.inviteToWorkspace.token}`));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
spinner.fail(`Failed to send invitation: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
invite
|
|
34
|
+
.command('list [workspaceId]')
|
|
35
|
+
.description('List pending invitations')
|
|
36
|
+
.action(async (workspaceId) => {
|
|
37
|
+
const globalOptions = program.opts();
|
|
38
|
+
const spinner = !globalOptions.json ? ora('Fetching invitations...').start() : null;
|
|
39
|
+
try {
|
|
40
|
+
let query;
|
|
41
|
+
let variables = {};
|
|
42
|
+
if (workspaceId) {
|
|
43
|
+
query = `
|
|
44
|
+
query($workspaceId: ID!) {
|
|
45
|
+
workspaceInvitations(workspaceId: $workspaceId) {
|
|
46
|
+
id
|
|
47
|
+
email
|
|
48
|
+
role
|
|
49
|
+
expiresAt
|
|
50
|
+
createdAt
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
variables = { workspaceId };
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
query = `
|
|
58
|
+
query {
|
|
59
|
+
myInvitations {
|
|
60
|
+
id
|
|
61
|
+
email
|
|
62
|
+
role
|
|
63
|
+
expiresAt
|
|
64
|
+
createdAt
|
|
65
|
+
workspace {
|
|
66
|
+
name
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
const data = await graphqlRequest(query, variables);
|
|
73
|
+
const invitations = workspaceId ? data.workspaceInvitations : data.myInvitations;
|
|
74
|
+
if (spinner)
|
|
75
|
+
spinner.stop();
|
|
76
|
+
if (globalOptions.json) {
|
|
77
|
+
console.log(JSON.stringify(invitations, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (invitations.length === 0) {
|
|
81
|
+
console.log(chalk.yellow('No pending invitations found.'));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const table = new Table({
|
|
85
|
+
head: [chalk.cyan('ID'), chalk.cyan('Email'), chalk.cyan('Workspace'), chalk.cyan('Role'), chalk.cyan('Expires')],
|
|
86
|
+
colWidths: [36, 30, 20, 10, 25]
|
|
87
|
+
});
|
|
88
|
+
invitations.forEach((inv) => {
|
|
89
|
+
table.push([
|
|
90
|
+
inv.id,
|
|
91
|
+
inv.email,
|
|
92
|
+
inv.workspace?.name || 'N/A',
|
|
93
|
+
inv.role,
|
|
94
|
+
inv.expiresAt
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
console.log(table.toString());
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (spinner)
|
|
101
|
+
spinner.fail(`Failed to fetch invitations: ${error.message}`);
|
|
102
|
+
else
|
|
103
|
+
console.error(JSON.stringify({ error: error.message }));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
invite
|
|
107
|
+
.command('accept <token>')
|
|
108
|
+
.description('Accept a workspace invitation')
|
|
109
|
+
.action(async (token) => {
|
|
110
|
+
const spinner = ora('Accepting invitation...').start();
|
|
111
|
+
try {
|
|
112
|
+
const mutation = `
|
|
113
|
+
mutation($input: AcceptInvitationInput!) {
|
|
114
|
+
acceptWorkspaceInvitation(input: $input) {
|
|
115
|
+
id
|
|
116
|
+
workspace {
|
|
117
|
+
name
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
const data = await graphqlRequest(mutation, { input: { token } });
|
|
123
|
+
spinner.succeed(chalk.green(`Successfully joined workspace: ${data.acceptWorkspaceInvitation.workspace.name}`));
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
spinner.fail(`Failed to accept invitation: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
invite
|
|
130
|
+
.command('cancel <id>')
|
|
131
|
+
.description('Cancel a pending invitation')
|
|
132
|
+
.action(async (id) => {
|
|
133
|
+
const spinner = ora('Cancelling invitation...').start();
|
|
134
|
+
try {
|
|
135
|
+
const mutation = `
|
|
136
|
+
mutation($id: ID!) {
|
|
137
|
+
cancelWorkspaceInvitation(id: $id)
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
await graphqlRequest(mutation, { id });
|
|
141
|
+
spinner.succeed(chalk.green('Invitation cancelled.'));
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
spinner.fail(`Failed to cancel invitation: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|