superintent 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.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/bin/superintent.js +2 -0
- package/dist/commands/extract.d.ts +2 -0
- package/dist/commands/extract.js +66 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +56 -0
- package/dist/commands/knowledge.d.ts +2 -0
- package/dist/commands/knowledge.js +647 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +153 -0
- package/dist/commands/spec.d.ts +2 -0
- package/dist/commands/spec.js +283 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +43 -0
- package/dist/commands/ticket.d.ts +4 -0
- package/dist/commands/ticket.js +942 -0
- package/dist/commands/ui.d.ts +2 -0
- package/dist/commands/ui.js +954 -0
- package/dist/db/client.d.ts +4 -0
- package/dist/db/client.js +26 -0
- package/dist/db/init-schema.d.ts +2 -0
- package/dist/db/init-schema.js +28 -0
- package/dist/db/parsers.d.ts +24 -0
- package/dist/db/parsers.js +79 -0
- package/dist/db/schema.d.ts +7 -0
- package/dist/db/schema.js +64 -0
- package/dist/db/usage.d.ts +8 -0
- package/dist/db/usage.js +24 -0
- package/dist/embed/model.d.ts +5 -0
- package/dist/embed/model.js +34 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +31 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.js +1 -0
- package/dist/ui/components/index.d.ts +6 -0
- package/dist/ui/components/index.js +13 -0
- package/dist/ui/components/knowledge.d.ts +33 -0
- package/dist/ui/components/knowledge.js +238 -0
- package/dist/ui/components/layout.d.ts +1 -0
- package/dist/ui/components/layout.js +323 -0
- package/dist/ui/components/search.d.ts +15 -0
- package/dist/ui/components/search.js +114 -0
- package/dist/ui/components/spec.d.ts +11 -0
- package/dist/ui/components/spec.js +253 -0
- package/dist/ui/components/ticket.d.ts +90 -0
- package/dist/ui/components/ticket.js +604 -0
- package/dist/ui/components/utils.d.ts +26 -0
- package/dist/ui/components/utils.js +34 -0
- package/dist/ui/styles.css +2 -0
- package/dist/utils/cli.d.ts +21 -0
- package/dist/utils/cli.js +31 -0
- package/dist/utils/config.d.ts +12 -0
- package/dist/utils/config.js +116 -0
- package/dist/utils/id.d.ts +6 -0
- package/dist/utils/id.js +13 -0
- package/dist/utils/io.d.ts +8 -0
- package/dist/utils/io.js +15 -0
- package/package.json +60 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getClient, closeClient } from '../db/client.js';
|
|
3
|
+
import { parseSearchRow } from '../db/parsers.js';
|
|
4
|
+
import { trackUsage } from '../db/usage.js';
|
|
5
|
+
import { embed } from '../embed/model.js';
|
|
6
|
+
export const searchCommand = new Command('search')
|
|
7
|
+
.description('Semantic search knowledge base')
|
|
8
|
+
.argument('<query>', 'Search query')
|
|
9
|
+
.option('--namespace <namespace>', 'Filter by namespace (project)')
|
|
10
|
+
.option('--category <category>', 'Filter by category')
|
|
11
|
+
.option('--ticket-type <type>', 'Filter by origin ticket type (feature|bugfix|refactor|docs|chore|test)')
|
|
12
|
+
.option('--tags <tags...>', 'Filter by tags (OR logic)')
|
|
13
|
+
.option('--min-score <n>', 'Minimum similarity score 0-1', '0')
|
|
14
|
+
.option('--limit <n>', 'Max results', '5')
|
|
15
|
+
.action(async (query, options) => {
|
|
16
|
+
try {
|
|
17
|
+
const client = await getClient();
|
|
18
|
+
// Generate query embedding
|
|
19
|
+
const queryEmbedding = await embed(query);
|
|
20
|
+
// Build query with filters
|
|
21
|
+
const conditions = ['k.active = 1'];
|
|
22
|
+
const limit = parseInt(options.limit, 10);
|
|
23
|
+
const topK = limit * 2; // Fetch extra for filtering
|
|
24
|
+
const args = [
|
|
25
|
+
JSON.stringify(queryEmbedding),
|
|
26
|
+
JSON.stringify(queryEmbedding),
|
|
27
|
+
];
|
|
28
|
+
if (options.namespace) {
|
|
29
|
+
conditions.push('k.namespace = ?');
|
|
30
|
+
args.push(options.namespace);
|
|
31
|
+
}
|
|
32
|
+
if (options.category) {
|
|
33
|
+
conditions.push('k.category = ?');
|
|
34
|
+
args.push(options.category);
|
|
35
|
+
}
|
|
36
|
+
if (options.ticketType) {
|
|
37
|
+
conditions.push('k.origin_ticket_type = ?');
|
|
38
|
+
args.push(options.ticketType);
|
|
39
|
+
}
|
|
40
|
+
// Build SQL with vector search (k must be literal, not bound parameter)
|
|
41
|
+
const sql = `
|
|
42
|
+
SELECT
|
|
43
|
+
k.id, k.namespace, k.chunk_index, k.title, k.content,
|
|
44
|
+
k.category, k.tags, k.source, k.origin_ticket_id, k.origin_ticket_type, k.confidence, k.active, k.decision_scope,
|
|
45
|
+
k.usage_count, k.last_used_at, k.created_at,
|
|
46
|
+
vector_distance_cos(k.embedding, vector32(?)) as distance
|
|
47
|
+
FROM vector_top_k('knowledge_embedding_idx', vector32(?), ${topK}) AS v
|
|
48
|
+
JOIN knowledge k ON k.rowid = v.id
|
|
49
|
+
WHERE ${conditions.join(' AND ')}
|
|
50
|
+
ORDER BY distance ASC
|
|
51
|
+
LIMIT ${limit}
|
|
52
|
+
`;
|
|
53
|
+
const result = await client.execute({ sql, args });
|
|
54
|
+
// Filter by min score and tags
|
|
55
|
+
const minScore = parseFloat(options.minScore);
|
|
56
|
+
let results = result.rows
|
|
57
|
+
.map((row) => parseSearchRow(row))
|
|
58
|
+
.filter((r) => r.score >= minScore);
|
|
59
|
+
// Filter by tags (OR logic) if provided
|
|
60
|
+
if (options.tags && options.tags.length > 0) {
|
|
61
|
+
results = results.filter((r) => {
|
|
62
|
+
if (!r.tags)
|
|
63
|
+
return false;
|
|
64
|
+
return options.tags.some((tag) => r.tags.includes(tag));
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Track usage for returned results
|
|
68
|
+
await trackUsage(results.map(r => r.id));
|
|
69
|
+
closeClient();
|
|
70
|
+
const response = {
|
|
71
|
+
success: true,
|
|
72
|
+
data: {
|
|
73
|
+
query,
|
|
74
|
+
results,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
console.log(JSON.stringify(response));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// Fallback to non-indexed search if vector index doesn't exist or fails
|
|
81
|
+
const errorMessage = error.message;
|
|
82
|
+
if (errorMessage.includes('vector_top_k') || errorMessage.includes('vector index') || errorMessage.includes('no such table')) {
|
|
83
|
+
try {
|
|
84
|
+
const client = await getClient();
|
|
85
|
+
const queryEmbedding = await embed(query);
|
|
86
|
+
const conditions = ['active = 1'];
|
|
87
|
+
const args = [JSON.stringify(queryEmbedding)];
|
|
88
|
+
if (options.namespace) {
|
|
89
|
+
conditions.push('namespace = ?');
|
|
90
|
+
args.push(options.namespace);
|
|
91
|
+
}
|
|
92
|
+
if (options.category) {
|
|
93
|
+
conditions.push('category = ?');
|
|
94
|
+
args.push(options.category);
|
|
95
|
+
}
|
|
96
|
+
if (options.ticketType) {
|
|
97
|
+
conditions.push('origin_ticket_type = ?');
|
|
98
|
+
args.push(options.ticketType);
|
|
99
|
+
}
|
|
100
|
+
const sql = `
|
|
101
|
+
SELECT
|
|
102
|
+
id, namespace, chunk_index, title, content,
|
|
103
|
+
category, tags, source, origin_ticket_id, origin_ticket_type, confidence, active, decision_scope,
|
|
104
|
+
usage_count, last_used_at, created_at,
|
|
105
|
+
vector_distance_cos(embedding, vector32(?)) as distance
|
|
106
|
+
FROM knowledge
|
|
107
|
+
WHERE ${conditions.join(' AND ')}
|
|
108
|
+
ORDER BY distance ASC
|
|
109
|
+
LIMIT ?
|
|
110
|
+
`;
|
|
111
|
+
args.push(parseInt(options.limit, 10));
|
|
112
|
+
const result = await client.execute({ sql, args });
|
|
113
|
+
const minScore = parseFloat(options.minScore);
|
|
114
|
+
let results = result.rows
|
|
115
|
+
.map((row) => parseSearchRow(row))
|
|
116
|
+
.filter((r) => r.score >= minScore);
|
|
117
|
+
if (options.tags && options.tags.length > 0) {
|
|
118
|
+
results = results.filter((r) => {
|
|
119
|
+
if (!r.tags)
|
|
120
|
+
return false;
|
|
121
|
+
return options.tags.some((tag) => r.tags.includes(tag));
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Track usage for returned results
|
|
125
|
+
await trackUsage(results.map(r => r.id));
|
|
126
|
+
closeClient();
|
|
127
|
+
const response = {
|
|
128
|
+
success: true,
|
|
129
|
+
data: {
|
|
130
|
+
query,
|
|
131
|
+
results,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
console.log(JSON.stringify(response));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
catch (fallbackError) {
|
|
138
|
+
const response = {
|
|
139
|
+
success: false,
|
|
140
|
+
error: `Search failed: ${fallbackError.message}`,
|
|
141
|
+
};
|
|
142
|
+
console.log(JSON.stringify(response));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const response = {
|
|
147
|
+
success: false,
|
|
148
|
+
error: `Search failed: ${errorMessage}`,
|
|
149
|
+
};
|
|
150
|
+
console.log(JSON.stringify(response));
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getClient, closeClient } from '../db/client.js';
|
|
3
|
+
import { parseSpecRow } from '../db/parsers.js';
|
|
4
|
+
import { readStdin } from '../utils/io.js';
|
|
5
|
+
import { generateId } from '../utils/id.js';
|
|
6
|
+
/**
|
|
7
|
+
* Parse markdown spec format matching SKILL.md:
|
|
8
|
+
*
|
|
9
|
+
* # {spec name}
|
|
10
|
+
*
|
|
11
|
+
* ## Summary
|
|
12
|
+
* ...rest of spec content...
|
|
13
|
+
*/
|
|
14
|
+
function parseMarkdownSpec(markdown) {
|
|
15
|
+
const lines = markdown.split('\n');
|
|
16
|
+
const result = { title: '', content: '' };
|
|
17
|
+
let contentStartIndex = 0;
|
|
18
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19
|
+
const trimmed = lines[i].trim();
|
|
20
|
+
// Parse title: # {spec name}
|
|
21
|
+
if (trimmed.startsWith('# ') && !trimmed.startsWith('## ')) {
|
|
22
|
+
result.title = trimmed.substring(2).trim();
|
|
23
|
+
contentStartIndex = i + 1;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Everything after the title line is content
|
|
28
|
+
result.content = lines.slice(contentStartIndex).join('\n').trim();
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
export const specCommand = new Command('spec')
|
|
32
|
+
.description('Manage specs');
|
|
33
|
+
// Create subcommand
|
|
34
|
+
specCommand
|
|
35
|
+
.command('create')
|
|
36
|
+
.description('Create a new spec from stdin')
|
|
37
|
+
.option('--stdin', 'Read spec markdown from stdin')
|
|
38
|
+
.option('--title <title>', 'Spec title')
|
|
39
|
+
.option('--content <content>', 'Spec content')
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
try {
|
|
42
|
+
let id;
|
|
43
|
+
let title;
|
|
44
|
+
let content;
|
|
45
|
+
if (options.stdin) {
|
|
46
|
+
const markdown = await readStdin();
|
|
47
|
+
const parsed = parseMarkdownSpec(markdown);
|
|
48
|
+
// Field-level validation
|
|
49
|
+
const missing = [];
|
|
50
|
+
if (!parsed.title)
|
|
51
|
+
missing.push('title: Missing # Title header');
|
|
52
|
+
if (!parsed.content)
|
|
53
|
+
missing.push('content: No content found after header');
|
|
54
|
+
if (missing.length > 0) {
|
|
55
|
+
const response = {
|
|
56
|
+
success: false,
|
|
57
|
+
error: missing.join('; '),
|
|
58
|
+
};
|
|
59
|
+
console.log(JSON.stringify(response));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
id = generateId('SPEC');
|
|
63
|
+
title = parsed.title;
|
|
64
|
+
content = parsed.content;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (!options.title) {
|
|
68
|
+
const response = {
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'Required: --title (or use --stdin)',
|
|
71
|
+
};
|
|
72
|
+
console.log(JSON.stringify(response));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
id = generateId('SPEC');
|
|
76
|
+
title = options.title;
|
|
77
|
+
content = options.content || '';
|
|
78
|
+
}
|
|
79
|
+
const client = await getClient();
|
|
80
|
+
await client.execute({
|
|
81
|
+
sql: `INSERT INTO specs (id, title, content) VALUES (?, ?, ?)`,
|
|
82
|
+
args: [id, title, content],
|
|
83
|
+
});
|
|
84
|
+
closeClient();
|
|
85
|
+
const response = {
|
|
86
|
+
success: true,
|
|
87
|
+
data: { id, status: 'created' },
|
|
88
|
+
};
|
|
89
|
+
console.log(JSON.stringify(response));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const response = {
|
|
93
|
+
success: false,
|
|
94
|
+
error: `Failed to create spec: ${error.message}`,
|
|
95
|
+
};
|
|
96
|
+
console.log(JSON.stringify(response));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Get subcommand
|
|
101
|
+
specCommand
|
|
102
|
+
.command('get')
|
|
103
|
+
.description('Get a spec by ID')
|
|
104
|
+
.argument('<id>', 'Spec ID')
|
|
105
|
+
.action(async (id) => {
|
|
106
|
+
try {
|
|
107
|
+
const client = await getClient();
|
|
108
|
+
const result = await client.execute({
|
|
109
|
+
sql: 'SELECT * FROM specs WHERE id = ?',
|
|
110
|
+
args: [id],
|
|
111
|
+
});
|
|
112
|
+
closeClient();
|
|
113
|
+
if (result.rows.length === 0) {
|
|
114
|
+
const response = {
|
|
115
|
+
success: false,
|
|
116
|
+
error: `Spec ${id} not found`,
|
|
117
|
+
};
|
|
118
|
+
console.log(JSON.stringify(response));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const spec = parseSpecRow(result.rows[0]);
|
|
122
|
+
const response = {
|
|
123
|
+
success: true,
|
|
124
|
+
data: spec,
|
|
125
|
+
};
|
|
126
|
+
console.log(JSON.stringify(response));
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const response = {
|
|
130
|
+
success: false,
|
|
131
|
+
error: `Failed to get spec: ${error.message}`,
|
|
132
|
+
};
|
|
133
|
+
console.log(JSON.stringify(response));
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// List subcommand
|
|
138
|
+
specCommand
|
|
139
|
+
.command('list')
|
|
140
|
+
.description('List specs')
|
|
141
|
+
.option('--limit <n>', 'Limit results', '20')
|
|
142
|
+
.action(async (options) => {
|
|
143
|
+
try {
|
|
144
|
+
const client = await getClient();
|
|
145
|
+
const result = await client.execute({
|
|
146
|
+
sql: 'SELECT * FROM specs ORDER BY created_at DESC LIMIT ?',
|
|
147
|
+
args: [parseInt(options.limit, 10)],
|
|
148
|
+
});
|
|
149
|
+
closeClient();
|
|
150
|
+
const specs = result.rows.map((row) => parseSpecRow(row));
|
|
151
|
+
const response = {
|
|
152
|
+
success: true,
|
|
153
|
+
data: specs,
|
|
154
|
+
};
|
|
155
|
+
console.log(JSON.stringify(response));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const response = {
|
|
159
|
+
success: false,
|
|
160
|
+
error: `Failed to list specs: ${error.message}`,
|
|
161
|
+
};
|
|
162
|
+
console.log(JSON.stringify(response));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
// Update subcommand
|
|
167
|
+
specCommand
|
|
168
|
+
.command('update')
|
|
169
|
+
.description('Update a spec')
|
|
170
|
+
.argument('<id>', 'Spec ID')
|
|
171
|
+
.option('--title <title>', 'New title')
|
|
172
|
+
.option('--content-stdin', 'Read new content from stdin')
|
|
173
|
+
.action(async (id, options) => {
|
|
174
|
+
try {
|
|
175
|
+
const client = await getClient();
|
|
176
|
+
const updates = [];
|
|
177
|
+
const args = [];
|
|
178
|
+
// Read stdin — parse as full spec markdown if it has a # title
|
|
179
|
+
let stdinParsed;
|
|
180
|
+
if (options.contentStdin) {
|
|
181
|
+
const raw = await readStdin();
|
|
182
|
+
const parsed = parseMarkdownSpec(raw);
|
|
183
|
+
if (parsed.title) {
|
|
184
|
+
stdinParsed = parsed;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
// Plain content text (no title header)
|
|
188
|
+
updates.push('content = ?');
|
|
189
|
+
args.push(raw.trim());
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (options.title) {
|
|
193
|
+
updates.push('title = ?');
|
|
194
|
+
args.push(options.title);
|
|
195
|
+
}
|
|
196
|
+
else if (stdinParsed?.title) {
|
|
197
|
+
updates.push('title = ?');
|
|
198
|
+
args.push(stdinParsed.title);
|
|
199
|
+
}
|
|
200
|
+
if (stdinParsed?.content) {
|
|
201
|
+
updates.push('content = ?');
|
|
202
|
+
args.push(stdinParsed.content);
|
|
203
|
+
}
|
|
204
|
+
if (updates.length === 0) {
|
|
205
|
+
closeClient();
|
|
206
|
+
const response = {
|
|
207
|
+
success: false,
|
|
208
|
+
error: 'No updates provided. Use --title or --content-stdin',
|
|
209
|
+
};
|
|
210
|
+
console.log(JSON.stringify(response));
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
updates.push("updated_at = datetime('now')");
|
|
214
|
+
args.push(id);
|
|
215
|
+
const result = await client.execute({
|
|
216
|
+
sql: `UPDATE specs SET ${updates.join(', ')} WHERE id = ?`,
|
|
217
|
+
args,
|
|
218
|
+
});
|
|
219
|
+
closeClient();
|
|
220
|
+
if (result.rowsAffected === 0) {
|
|
221
|
+
const response = {
|
|
222
|
+
success: false,
|
|
223
|
+
error: `Spec ${id} not found`,
|
|
224
|
+
};
|
|
225
|
+
console.log(JSON.stringify(response));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
const response = {
|
|
229
|
+
success: true,
|
|
230
|
+
data: { id, status: 'updated' },
|
|
231
|
+
};
|
|
232
|
+
console.log(JSON.stringify(response));
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
const response = {
|
|
236
|
+
success: false,
|
|
237
|
+
error: `Failed to update spec: ${error.message}`,
|
|
238
|
+
};
|
|
239
|
+
console.log(JSON.stringify(response));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
// Delete subcommand
|
|
244
|
+
specCommand
|
|
245
|
+
.command('delete')
|
|
246
|
+
.description('Delete a spec by ID')
|
|
247
|
+
.argument('<id>', 'Spec ID')
|
|
248
|
+
.action(async (id) => {
|
|
249
|
+
try {
|
|
250
|
+
const client = await getClient();
|
|
251
|
+
const existing = await client.execute({
|
|
252
|
+
sql: 'SELECT id FROM specs WHERE id = ?',
|
|
253
|
+
args: [id],
|
|
254
|
+
});
|
|
255
|
+
if (existing.rows.length === 0) {
|
|
256
|
+
closeClient();
|
|
257
|
+
const response = {
|
|
258
|
+
success: false,
|
|
259
|
+
error: `Spec ${id} not found`,
|
|
260
|
+
};
|
|
261
|
+
console.log(JSON.stringify(response));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
await client.execute({
|
|
265
|
+
sql: 'DELETE FROM specs WHERE id = ?',
|
|
266
|
+
args: [id],
|
|
267
|
+
});
|
|
268
|
+
closeClient();
|
|
269
|
+
const response = {
|
|
270
|
+
success: true,
|
|
271
|
+
data: { id, status: 'deleted' },
|
|
272
|
+
};
|
|
273
|
+
console.log(JSON.stringify(response));
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
const response = {
|
|
277
|
+
success: false,
|
|
278
|
+
error: `Failed to delete spec: ${error.message}`,
|
|
279
|
+
};
|
|
280
|
+
console.log(JSON.stringify(response));
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadConfig, configExists } from '../utils/config.js';
|
|
3
|
+
import { getClient, closeClient } from '../db/client.js';
|
|
4
|
+
export const statusCommand = new Command('status')
|
|
5
|
+
.description('Check Turso connection status')
|
|
6
|
+
.action(async () => {
|
|
7
|
+
try {
|
|
8
|
+
// Check config
|
|
9
|
+
if (!configExists()) {
|
|
10
|
+
const response = {
|
|
11
|
+
success: false,
|
|
12
|
+
error: 'Not configured. Run: superintent init --url <url> --token <token>',
|
|
13
|
+
};
|
|
14
|
+
console.log(JSON.stringify(response));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
// Test connection
|
|
19
|
+
const client = await getClient();
|
|
20
|
+
// Get counts
|
|
21
|
+
const ticketCount = await client.execute('SELECT COUNT(*) as count FROM tickets');
|
|
22
|
+
const knowledgeCount = await client.execute('SELECT COUNT(*) as count FROM knowledge');
|
|
23
|
+
closeClient();
|
|
24
|
+
const response = {
|
|
25
|
+
success: true,
|
|
26
|
+
data: {
|
|
27
|
+
connected: true,
|
|
28
|
+
url: config.url.replace(/\/\/.*:.*@/, '//***@'), // Hide token in URL if present
|
|
29
|
+
tickets: ticketCount.rows[0].count,
|
|
30
|
+
knowledge: knowledgeCount.rows[0].count,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
console.log(JSON.stringify(response));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const response = {
|
|
37
|
+
success: false,
|
|
38
|
+
error: `Connection failed: ${error.message}`,
|
|
39
|
+
};
|
|
40
|
+
console.log(JSON.stringify(response));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
});
|