ownkey 0.1.1 → 0.2.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/.prettierrc +9 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/commands/apply.d.ts +3 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +26 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +142 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/scan.d.ts +3 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +97 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/suggest.d.ts +3 -0
- package/dist/commands/suggest.d.ts.map +1 -0
- package/dist/commands/suggest.js +34 -0
- package/dist/commands/suggest.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +74 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/keychain.d.ts +14 -0
- package/dist/config/keychain.d.ts.map +1 -0
- package/dist/config/keychain.js +112 -0
- package/dist/config/keychain.js.map +1 -0
- package/dist/config/manager.d.ts +21 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +144 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/core/scanner.d.ts +17 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +150 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/db/client.d.ts +15 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +130 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/operations.d.ts +14 -0
- package/dist/db/operations.d.ts.map +1 -0
- package/dist/db/operations.js +175 -0
- package/dist/db/operations.js.map +1 -0
- package/dist/db/schema.d.ts +683 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +107 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -2
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +138 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/encryption.d.ts +6 -0
- package/dist/utils/encryption.d.ts.map +1 -0
- package/dist/utils/encryption.js +39 -0
- package/dist/utils/encryption.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +56 -29
- package/src/commands/apply.ts +27 -0
- package/src/commands/config.ts +154 -0
- package/src/commands/scan.ts +114 -0
- package/src/commands/suggest.ts +37 -0
- package/src/config/defaults.ts +80 -0
- package/src/config/keychain.ts +125 -0
- package/src/config/manager.ts +166 -0
- package/src/core/scanner.ts +174 -0
- package/src/db/client.ts +151 -0
- package/src/db/migrations/001_init.sql +80 -0
- package/src/db/operations.ts +217 -0
- package/src/db/schema.ts +118 -0
- package/src/index.ts +74 -0
- package/src/types/index.ts +198 -0
- package/src/utils/encryption.ts +54 -0
- package/src/utils/logger.ts +111 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { eq, desc } from 'drizzle-orm';
|
|
2
|
+
import { dbClient } from './client.js';
|
|
3
|
+
import * as schema from './schema.js';
|
|
4
|
+
import {
|
|
5
|
+
Project,
|
|
6
|
+
Run,
|
|
7
|
+
SuggestionRecord,
|
|
8
|
+
RunStatus,
|
|
9
|
+
SuggestionAction,
|
|
10
|
+
} from '../types/index.js';
|
|
11
|
+
import { logger } from '../utils/logger.js';
|
|
12
|
+
|
|
13
|
+
export class DatabaseOperations {
|
|
14
|
+
// Projects
|
|
15
|
+
async createProject(name: string, rootPath: string): Promise<Project | null> {
|
|
16
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
17
|
+
logger.verbose('Skipping project creation (local-only mode)');
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const db = dbClient.getDb();
|
|
22
|
+
const [project] = await db
|
|
23
|
+
.insert(schema.projects)
|
|
24
|
+
.values({ name, rootPath })
|
|
25
|
+
.returning();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
id: project.id,
|
|
29
|
+
name: project.name,
|
|
30
|
+
rootPath: project.rootPath,
|
|
31
|
+
createdAt: project.createdAt,
|
|
32
|
+
updatedAt: project.updatedAt,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async getProjectByPath(rootPath: string): Promise<Project | null> {
|
|
37
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const db = dbClient.getDb();
|
|
42
|
+
const [project] = await db
|
|
43
|
+
.select()
|
|
44
|
+
.from(schema.projects)
|
|
45
|
+
.where(eq(schema.projects.rootPath, rootPath))
|
|
46
|
+
.limit(1);
|
|
47
|
+
|
|
48
|
+
if (!project) return null;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
id: project.id,
|
|
52
|
+
name: project.name,
|
|
53
|
+
rootPath: project.rootPath,
|
|
54
|
+
createdAt: project.createdAt,
|
|
55
|
+
updatedAt: project.updatedAt,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Runs
|
|
60
|
+
async createRun(projectId: string, command: string): Promise<Run | null> {
|
|
61
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
62
|
+
logger.verbose('Skipping run creation (local-only mode)');
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const db = dbClient.getDb();
|
|
67
|
+
const [run] = await db
|
|
68
|
+
.insert(schema.runs)
|
|
69
|
+
.values({ projectId, command, status: 'running' })
|
|
70
|
+
.returning();
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
id: run.id,
|
|
74
|
+
projectId: run.projectId,
|
|
75
|
+
command: run.command,
|
|
76
|
+
status: run.status as RunStatus,
|
|
77
|
+
summary: run.summary || undefined,
|
|
78
|
+
startedAt: run.startedAt,
|
|
79
|
+
finishedAt: run.finishedAt || undefined,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async updateRunStatus(
|
|
84
|
+
runId: string,
|
|
85
|
+
status: RunStatus,
|
|
86
|
+
summary?: string
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const db = dbClient.getDb();
|
|
93
|
+
await db
|
|
94
|
+
.update(schema.runs)
|
|
95
|
+
.set({ status, summary, finishedAt: new Date() })
|
|
96
|
+
.where(eq(schema.runs.id, runId));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async getRecentRuns(limit: number = 10): Promise<Run[]> {
|
|
100
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const db = dbClient.getDb();
|
|
105
|
+
const runs = await db
|
|
106
|
+
.select()
|
|
107
|
+
.from(schema.runs)
|
|
108
|
+
.orderBy(desc(schema.runs.startedAt))
|
|
109
|
+
.limit(limit);
|
|
110
|
+
|
|
111
|
+
return runs.map((run) => ({
|
|
112
|
+
id: run.id,
|
|
113
|
+
projectId: run.projectId,
|
|
114
|
+
command: run.command,
|
|
115
|
+
status: run.status as RunStatus,
|
|
116
|
+
summary: run.summary || undefined,
|
|
117
|
+
startedAt: run.startedAt,
|
|
118
|
+
finishedAt: run.finishedAt || undefined,
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Suggestions
|
|
123
|
+
async createSuggestion(
|
|
124
|
+
projectId: string,
|
|
125
|
+
suggestion: Omit<SuggestionRecord, 'id' | 'projectId' | 'createdAt'>
|
|
126
|
+
): Promise<SuggestionRecord | null> {
|
|
127
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
128
|
+
logger.verbose('Skipping suggestion storage (local-only mode)');
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const db = dbClient.getDb();
|
|
133
|
+
const [created] = await db
|
|
134
|
+
.insert(schema.suggestions)
|
|
135
|
+
.values({
|
|
136
|
+
projectId,
|
|
137
|
+
type: suggestion.type,
|
|
138
|
+
severity: suggestion.severity,
|
|
139
|
+
content: suggestion.content,
|
|
140
|
+
diff: suggestion.diff,
|
|
141
|
+
filePath: suggestion.filePath,
|
|
142
|
+
rationale: suggestion.rationale,
|
|
143
|
+
tags: suggestion.tags,
|
|
144
|
+
})
|
|
145
|
+
.returning();
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
id: created.id,
|
|
149
|
+
projectId: created.projectId,
|
|
150
|
+
type: created.type as any,
|
|
151
|
+
severity: created.severity as any,
|
|
152
|
+
content: created.content,
|
|
153
|
+
diff: created.diff,
|
|
154
|
+
filePath: created.filePath,
|
|
155
|
+
rationale: created.rationale,
|
|
156
|
+
tags: created.tags as any,
|
|
157
|
+
createdAt: created.createdAt,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async getSuggestionById(id: string): Promise<SuggestionRecord | null> {
|
|
162
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const db = dbClient.getDb();
|
|
167
|
+
const [suggestion] = await db
|
|
168
|
+
.select()
|
|
169
|
+
.from(schema.suggestions)
|
|
170
|
+
.where(eq(schema.suggestions.id, id))
|
|
171
|
+
.limit(1);
|
|
172
|
+
|
|
173
|
+
if (!suggestion) return null;
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
id: suggestion.id,
|
|
177
|
+
projectId: suggestion.projectId,
|
|
178
|
+
type: suggestion.type as any,
|
|
179
|
+
severity: suggestion.severity as any,
|
|
180
|
+
content: suggestion.content,
|
|
181
|
+
diff: suggestion.diff,
|
|
182
|
+
filePath: suggestion.filePath,
|
|
183
|
+
rationale: suggestion.rationale,
|
|
184
|
+
tags: suggestion.tags as any,
|
|
185
|
+
createdAt: suggestion.createdAt,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async linkRunSuggestion(runId: string, suggestionId: string): Promise<void> {
|
|
190
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const db = dbClient.getDb();
|
|
195
|
+
await db.insert(schema.runSuggestions).values({ runId, suggestionId });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Suggestion History
|
|
199
|
+
async recordSuggestionAction(
|
|
200
|
+
suggestionId: string,
|
|
201
|
+
action: SuggestionAction,
|
|
202
|
+
details?: string
|
|
203
|
+
): Promise<void> {
|
|
204
|
+
if (dbClient.isLocalOnlyMode()) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const db = dbClient.getDb();
|
|
209
|
+
await db.insert(schema.suggestionHistory).values({
|
|
210
|
+
suggestionId,
|
|
211
|
+
action,
|
|
212
|
+
details,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const dbOps = new DatabaseOperations();
|
package/src/db/schema.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { pgTable, uuid, text, timestamp, jsonb, integer } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { relations } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
// Projects table
|
|
5
|
+
export const projects = pgTable('projects', {
|
|
6
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
7
|
+
name: text('name').notNull(),
|
|
8
|
+
rootPath: text('root_path').notNull(),
|
|
9
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
10
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Runs table
|
|
14
|
+
export const runs = pgTable('runs', {
|
|
15
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
16
|
+
projectId: uuid('project_id')
|
|
17
|
+
.references(() => projects.id)
|
|
18
|
+
.notNull(),
|
|
19
|
+
command: text('command').notNull(),
|
|
20
|
+
status: text('status').notNull(), // 'running' | 'completed' | 'failed'
|
|
21
|
+
summary: text('summary'),
|
|
22
|
+
startedAt: timestamp('started_at').defaultNow().notNull(),
|
|
23
|
+
finishedAt: timestamp('finished_at'),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Suggestions table
|
|
27
|
+
export const suggestions = pgTable('suggestions', {
|
|
28
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
29
|
+
projectId: uuid('project_id')
|
|
30
|
+
.references(() => projects.id)
|
|
31
|
+
.notNull(),
|
|
32
|
+
type: text('type').notNull(), // 'bug' | 'security' | 'performance' | etc.
|
|
33
|
+
severity: text('severity').notNull(), // 'critical' | 'high' | 'medium' | 'low' | 'info'
|
|
34
|
+
content: text('content').notNull(),
|
|
35
|
+
diff: text('diff').notNull(),
|
|
36
|
+
filePath: text('file_path').notNull(),
|
|
37
|
+
rationale: text('rationale').notNull(),
|
|
38
|
+
tags: jsonb('tags'),
|
|
39
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Suggestion history table
|
|
43
|
+
export const suggestionHistory = pgTable('suggestion_history', {
|
|
44
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
45
|
+
suggestionId: uuid('suggestion_id')
|
|
46
|
+
.references(() => suggestions.id)
|
|
47
|
+
.notNull(),
|
|
48
|
+
action: text('action').notNull(), // 'applied' | 'rejected' | 'undone'
|
|
49
|
+
details: text('details'),
|
|
50
|
+
actedAt: timestamp('acted_at').defaultNow().notNull(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Run suggestions junction table
|
|
54
|
+
export const runSuggestions = pgTable('run_suggestions', {
|
|
55
|
+
runId: uuid('run_id')
|
|
56
|
+
.references(() => runs.id)
|
|
57
|
+
.notNull(),
|
|
58
|
+
suggestionId: uuid('suggestion_id')
|
|
59
|
+
.references(() => suggestions.id)
|
|
60
|
+
.notNull(),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// User API keys table (for fingerprints only)
|
|
64
|
+
export const userApiKeys = pgTable('user_api_keys', {
|
|
65
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
66
|
+
projectId: uuid('project_id').references(() => projects.id),
|
|
67
|
+
provider: text('provider').notNull(),
|
|
68
|
+
keyFingerprint: text('key_fingerprint').notNull(),
|
|
69
|
+
addedAt: timestamp('added_at').defaultNow().notNull(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Schema migrations table
|
|
73
|
+
export const schemaMigrations = pgTable('schema_migrations', {
|
|
74
|
+
version: integer('version').primaryKey(),
|
|
75
|
+
appliedAt: timestamp('applied_at').defaultNow().notNull(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Relations
|
|
79
|
+
export const projectsRelations = relations(projects, ({ many }) => ({
|
|
80
|
+
runs: many(runs),
|
|
81
|
+
suggestions: many(suggestions),
|
|
82
|
+
apiKeys: many(userApiKeys),
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
export const runsRelations = relations(runs, ({ one, many }) => ({
|
|
86
|
+
project: one(projects, {
|
|
87
|
+
fields: [runs.projectId],
|
|
88
|
+
references: [projects.id],
|
|
89
|
+
}),
|
|
90
|
+
runSuggestions: many(runSuggestions),
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
export const suggestionsRelations = relations(suggestions, ({ one, many }) => ({
|
|
94
|
+
project: one(projects, {
|
|
95
|
+
fields: [suggestions.projectId],
|
|
96
|
+
references: [projects.id],
|
|
97
|
+
}),
|
|
98
|
+
history: many(suggestionHistory),
|
|
99
|
+
runSuggestions: many(runSuggestions),
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
export const suggestionHistoryRelations = relations(suggestionHistory, ({ one }) => ({
|
|
103
|
+
suggestion: one(suggestions, {
|
|
104
|
+
fields: [suggestionHistory.suggestionId],
|
|
105
|
+
references: [suggestions.id],
|
|
106
|
+
}),
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
export const runSuggestionsRelations = relations(runSuggestions, ({ one }) => ({
|
|
110
|
+
run: one(runs, {
|
|
111
|
+
fields: [runSuggestions.runId],
|
|
112
|
+
references: [runs.id],
|
|
113
|
+
}),
|
|
114
|
+
suggestion: one(suggestions, {
|
|
115
|
+
fields: [runSuggestions.suggestionId],
|
|
116
|
+
references: [suggestions.id],
|
|
117
|
+
}),
|
|
118
|
+
}));
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { logger } from './utils/logger.js';
|
|
5
|
+
import { configManager } from './config/manager.js';
|
|
6
|
+
import { dbClient } from './db/client.js';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
// Import commands
|
|
10
|
+
import { configCommand } from './commands/config.js';
|
|
11
|
+
import { scanCommand } from './commands/scan.js';
|
|
12
|
+
import { suggestCommand } from './commands/suggest.js';
|
|
13
|
+
import { applyCommand } from './commands/apply.js';
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('ownkey')
|
|
19
|
+
.description('Local-first AI coding agent for intelligent codebase analysis')
|
|
20
|
+
.version('0.2.0');
|
|
21
|
+
|
|
22
|
+
// Global options
|
|
23
|
+
program.option('--local-only', 'Run without database persistence');
|
|
24
|
+
program.option('--verbose', 'Enable verbose logging');
|
|
25
|
+
program.option('--debug', 'Enable debug logging');
|
|
26
|
+
program.option('--quiet', 'Suppress non-essential output');
|
|
27
|
+
|
|
28
|
+
// Commands
|
|
29
|
+
program.addCommand(configCommand);
|
|
30
|
+
program.addCommand(scanCommand);
|
|
31
|
+
program.addCommand(suggestCommand);
|
|
32
|
+
program.addCommand(applyCommand);
|
|
33
|
+
|
|
34
|
+
// Pre-parse hook to set up logging and check config
|
|
35
|
+
program.hook('preAction', async (thisCommand) => {
|
|
36
|
+
await logger.init();
|
|
37
|
+
|
|
38
|
+
const opts = thisCommand.opts();
|
|
39
|
+
|
|
40
|
+
// Set log level
|
|
41
|
+
if (opts.debug) {
|
|
42
|
+
logger.setLevel('debug');
|
|
43
|
+
} else if (opts.verbose) {
|
|
44
|
+
logger.setLevel('verbose');
|
|
45
|
+
} else if (opts.quiet) {
|
|
46
|
+
logger.setLevel('quiet');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Check if this is the config command
|
|
50
|
+
const isConfigCommand = thisCommand.name() === 'config';
|
|
51
|
+
|
|
52
|
+
// For non-config commands, check if config exists
|
|
53
|
+
if (!isConfigCommand) {
|
|
54
|
+
const configExists = await configManager.exists();
|
|
55
|
+
|
|
56
|
+
if (!configExists) {
|
|
57
|
+
console.log(chalk.yellow('\n⚠️ No configuration found.\n'));
|
|
58
|
+
console.log('OwnKey needs to be configured before use.');
|
|
59
|
+
console.log(chalk.cyan('\nRun: ownkey config\n'));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Connect to database (or enter local-only mode)
|
|
64
|
+
await dbClient.connect(opts.localOnly);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Post-action hook to clean up
|
|
69
|
+
program.hook('postAction', async () => {
|
|
70
|
+
await dbClient.disconnect();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Parse arguments
|
|
74
|
+
program.parse();
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
// Core type definitions for OwnKey CLI
|
|
2
|
+
|
|
3
|
+
export type LogLevel = 'quiet' | 'normal' | 'verbose' | 'debug';
|
|
4
|
+
|
|
5
|
+
export type AIProvider = 'openai' | 'anthropic' | 'ollama';
|
|
6
|
+
|
|
7
|
+
export type SuggestionType =
|
|
8
|
+
| 'bug'
|
|
9
|
+
| 'security'
|
|
10
|
+
| 'performance'
|
|
11
|
+
| 'refactor'
|
|
12
|
+
| 'style'
|
|
13
|
+
| 'architecture'
|
|
14
|
+
| 'test'
|
|
15
|
+
| 'documentation';
|
|
16
|
+
|
|
17
|
+
export type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
18
|
+
|
|
19
|
+
export type RunStatus = 'running' | 'completed' | 'failed';
|
|
20
|
+
|
|
21
|
+
export type SuggestionAction = 'applied' | 'rejected' | 'undone';
|
|
22
|
+
|
|
23
|
+
// Configuration types
|
|
24
|
+
export interface Config {
|
|
25
|
+
supabaseUrl?: string;
|
|
26
|
+
supabaseServiceRoleKey?: string;
|
|
27
|
+
providers: {
|
|
28
|
+
openai?: {
|
|
29
|
+
model?: string;
|
|
30
|
+
};
|
|
31
|
+
anthropic?: {
|
|
32
|
+
model?: string;
|
|
33
|
+
};
|
|
34
|
+
ollama?: {
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
model?: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
activeProvider?: AIProvider;
|
|
40
|
+
defaults: {
|
|
41
|
+
maxFileSize: string;
|
|
42
|
+
logLevel: LogLevel;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// File scanning types
|
|
47
|
+
export interface FileMetadata {
|
|
48
|
+
path: string;
|
|
49
|
+
relativePath: string;
|
|
50
|
+
size: number;
|
|
51
|
+
lastModified: Date;
|
|
52
|
+
extension: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface FileContent extends FileMetadata {
|
|
56
|
+
content: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ScanResult {
|
|
60
|
+
files: FileMetadata[];
|
|
61
|
+
totalSize: number;
|
|
62
|
+
skippedFiles: string[];
|
|
63
|
+
warnings: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Code context types
|
|
67
|
+
export interface CodeContext {
|
|
68
|
+
files: FileContent[];
|
|
69
|
+
projectInfo: ProjectMetadata;
|
|
70
|
+
totalTokens?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ProjectMetadata {
|
|
74
|
+
name: string;
|
|
75
|
+
rootPath: string;
|
|
76
|
+
fileCount: number;
|
|
77
|
+
language?: string;
|
|
78
|
+
framework?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// AI provider types
|
|
82
|
+
export interface Suggestion {
|
|
83
|
+
type: SuggestionType;
|
|
84
|
+
severity: Severity;
|
|
85
|
+
content: string;
|
|
86
|
+
diff: string;
|
|
87
|
+
filePath: string;
|
|
88
|
+
rationale: string;
|
|
89
|
+
tags?: Record<string, any>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AnalyzeOptions {
|
|
93
|
+
streaming?: boolean;
|
|
94
|
+
maxTokens?: number;
|
|
95
|
+
temperature?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface AIProviderInterface {
|
|
99
|
+
analyze(context: CodeContext, options?: AnalyzeOptions): AsyncGenerator<Suggestion>;
|
|
100
|
+
testConnection(): Promise<boolean>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Database types
|
|
104
|
+
export interface Project {
|
|
105
|
+
id: string;
|
|
106
|
+
name: string;
|
|
107
|
+
rootPath: string;
|
|
108
|
+
createdAt: Date;
|
|
109
|
+
updatedAt: Date;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface Run {
|
|
113
|
+
id: string;
|
|
114
|
+
projectId: string;
|
|
115
|
+
command: string;
|
|
116
|
+
status: RunStatus;
|
|
117
|
+
summary?: string;
|
|
118
|
+
startedAt: Date;
|
|
119
|
+
finishedAt?: Date;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface SuggestionRecord extends Suggestion {
|
|
123
|
+
id: string;
|
|
124
|
+
projectId: string;
|
|
125
|
+
createdAt: Date;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface SuggestionHistory {
|
|
129
|
+
id: string;
|
|
130
|
+
suggestionId: string;
|
|
131
|
+
action: SuggestionAction;
|
|
132
|
+
details?: string;
|
|
133
|
+
actedAt: Date;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Diff types
|
|
137
|
+
export interface DiffResult {
|
|
138
|
+
filePath: string;
|
|
139
|
+
hunks: DiffHunk[];
|
|
140
|
+
additions: number;
|
|
141
|
+
deletions: number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface DiffHunk {
|
|
145
|
+
oldStart: number;
|
|
146
|
+
oldLines: number;
|
|
147
|
+
newStart: number;
|
|
148
|
+
newLines: number;
|
|
149
|
+
lines: string[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Backup types
|
|
153
|
+
export interface BackupInfo {
|
|
154
|
+
filePath: string;
|
|
155
|
+
backupPath: string;
|
|
156
|
+
timestamp: Date;
|
|
157
|
+
size: number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Error types
|
|
161
|
+
export class OwnKeyError extends Error {
|
|
162
|
+
constructor(
|
|
163
|
+
message: string,
|
|
164
|
+
public code: string,
|
|
165
|
+
public details?: any
|
|
166
|
+
) {
|
|
167
|
+
super(message);
|
|
168
|
+
this.name = 'OwnKeyError';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export class ConfigError extends OwnKeyError {
|
|
173
|
+
constructor(message: string, details?: any) {
|
|
174
|
+
super(message, 'CONFIG_ERROR', details);
|
|
175
|
+
this.name = 'ConfigError';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export class DatabaseError extends OwnKeyError {
|
|
180
|
+
constructor(message: string, details?: any) {
|
|
181
|
+
super(message, 'DATABASE_ERROR', details);
|
|
182
|
+
this.name = 'DatabaseError';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export class AIProviderError extends OwnKeyError {
|
|
187
|
+
constructor(message: string, details?: any) {
|
|
188
|
+
super(message, 'AI_PROVIDER_ERROR', details);
|
|
189
|
+
this.name = 'AIProviderError';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export class ScanError extends OwnKeyError {
|
|
194
|
+
constructor(message: string, details?: any) {
|
|
195
|
+
super(message, 'SCAN_ERROR', details);
|
|
196
|
+
this.name = 'ScanError';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scrypt } from 'crypto';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
|
|
4
|
+
const scryptAsync = promisify(scrypt);
|
|
5
|
+
|
|
6
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
7
|
+
const KEY_LENGTH = 32;
|
|
8
|
+
const IV_LENGTH = 16;
|
|
9
|
+
const SALT_LENGTH = 32;
|
|
10
|
+
const TAG_LENGTH = 16;
|
|
11
|
+
|
|
12
|
+
export class Encryption {
|
|
13
|
+
private static async deriveKey(password: string, salt: Buffer): Promise<Buffer> {
|
|
14
|
+
return (await scryptAsync(password, salt, KEY_LENGTH)) as Buffer;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static async encrypt(text: string, password: string): Promise<string> {
|
|
18
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
19
|
+
const iv = randomBytes(IV_LENGTH);
|
|
20
|
+
const key = await this.deriveKey(password, salt);
|
|
21
|
+
|
|
22
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
23
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
24
|
+
encrypted += cipher.final('hex');
|
|
25
|
+
|
|
26
|
+
const tag = cipher.getAuthTag();
|
|
27
|
+
|
|
28
|
+
// Combine salt + iv + tag + encrypted
|
|
29
|
+
const result = Buffer.concat([salt, iv, tag, Buffer.from(encrypted, 'hex')]);
|
|
30
|
+
return result.toString('base64');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static async decrypt(encryptedData: string, password: string): Promise<string> {
|
|
34
|
+
const buffer = Buffer.from(encryptedData, 'base64');
|
|
35
|
+
|
|
36
|
+
const salt = buffer.subarray(0, SALT_LENGTH);
|
|
37
|
+
const iv = buffer.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
38
|
+
const tag = buffer.subarray(
|
|
39
|
+
SALT_LENGTH + IV_LENGTH,
|
|
40
|
+
SALT_LENGTH + IV_LENGTH + TAG_LENGTH
|
|
41
|
+
);
|
|
42
|
+
const encrypted = buffer.subarray(SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
43
|
+
|
|
44
|
+
const key = await this.deriveKey(password, salt);
|
|
45
|
+
|
|
46
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
47
|
+
decipher.setAuthTag(tag);
|
|
48
|
+
|
|
49
|
+
let decrypted = decipher.update(encrypted.toString('hex'), 'hex', 'utf8');
|
|
50
|
+
decrypted += decipher.final('utf8');
|
|
51
|
+
|
|
52
|
+
return decrypted;
|
|
53
|
+
}
|
|
54
|
+
}
|