k0ntext 3.3.1 → 3.6.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 +225 -26
- package/dist/agents/cleanup-agent.d.ts.map +1 -1
- package/dist/agents/cleanup-agent.js +18 -6
- package/dist/agents/cleanup-agent.js.map +1 -1
- package/dist/agents/drift-agent.d.ts +7 -0
- package/dist/agents/drift-agent.d.ts.map +1 -1
- package/dist/agents/drift-agent.js +29 -8
- package/dist/agents/drift-agent.js.map +1 -1
- package/dist/cli/commands/cleanup.d.ts.map +1 -1
- package/dist/cli/commands/cleanup.js +8 -1
- package/dist/cli/commands/cleanup.js.map +1 -1
- package/dist/cli/commands/drift-detect.d.ts.map +1 -1
- package/dist/cli/commands/drift-detect.js +21 -1
- package/dist/cli/commands/drift-detect.js.map +1 -1
- package/dist/cli/commands/embeddings-refresh.d.ts +11 -0
- package/dist/cli/commands/embeddings-refresh.d.ts.map +1 -0
- package/dist/cli/commands/embeddings-refresh.js +114 -0
- package/dist/cli/commands/embeddings-refresh.js.map +1 -0
- package/dist/cli/commands/migrate.d.ts +11 -0
- package/dist/cli/commands/migrate.d.ts.map +1 -0
- package/dist/cli/commands/migrate.js +195 -0
- package/dist/cli/commands/migrate.js.map +1 -0
- package/dist/cli/commands/restore.d.ts +12 -0
- package/dist/cli/commands/restore.d.ts.map +1 -0
- package/dist/cli/commands/restore.js +261 -0
- package/dist/cli/commands/restore.js.map +1 -0
- package/dist/cli/commands/sync-templates.d.ts +15 -0
- package/dist/cli/commands/sync-templates.d.ts.map +1 -0
- package/dist/cli/commands/sync-templates.js +181 -0
- package/dist/cli/commands/sync-templates.js.map +1 -0
- package/dist/cli/commands/version-check.d.ts +12 -0
- package/dist/cli/commands/version-check.d.ts.map +1 -0
- package/dist/cli/commands/version-check.js +133 -0
- package/dist/cli/commands/version-check.js.map +1 -0
- package/dist/cli/generate.d.ts +5 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +80 -16
- package/dist/cli/generate.js.map +1 -1
- package/dist/cli/index.js +215 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/repl/index.d.ts +1 -0
- package/dist/cli/repl/index.d.ts.map +1 -1
- package/dist/cli/repl/index.js +18 -6
- package/dist/cli/repl/index.js.map +1 -1
- package/dist/cli/utils/backup-manager.d.ts +94 -0
- package/dist/cli/utils/backup-manager.d.ts.map +1 -0
- package/dist/cli/utils/backup-manager.js +230 -0
- package/dist/cli/utils/backup-manager.js.map +1 -0
- package/dist/cli/utils/db-backup-manager.d.ts +55 -0
- package/dist/cli/utils/db-backup-manager.d.ts.map +1 -0
- package/dist/cli/utils/db-backup-manager.js +115 -0
- package/dist/cli/utils/db-backup-manager.js.map +1 -0
- package/dist/cli/utils/file-detector.d.ts +87 -0
- package/dist/cli/utils/file-detector.d.ts.map +1 -0
- package/dist/cli/utils/file-detector.js +131 -0
- package/dist/cli/utils/file-detector.js.map +1 -0
- package/dist/cli/utils/index.d.ts +9 -0
- package/dist/cli/utils/index.d.ts.map +1 -0
- package/dist/cli/utils/index.js +9 -0
- package/dist/cli/utils/index.js.map +1 -0
- package/dist/cli/utils/modification-prompt.d.ts +41 -0
- package/dist/cli/utils/modification-prompt.d.ts.map +1 -0
- package/dist/cli/utils/modification-prompt.js +84 -0
- package/dist/cli/utils/modification-prompt.js.map +1 -0
- package/dist/cli/version/checker.d.ts +47 -0
- package/dist/cli/version/checker.d.ts.map +1 -0
- package/dist/cli/version/checker.js +143 -0
- package/dist/cli/version/checker.js.map +1 -0
- package/dist/cli/version/comparator.d.ts +46 -0
- package/dist/cli/version/comparator.d.ts.map +1 -0
- package/dist/cli/version/comparator.js +99 -0
- package/dist/cli/version/comparator.js.map +1 -0
- package/dist/cli/version/index.d.ts +11 -0
- package/dist/cli/version/index.d.ts.map +1 -0
- package/dist/cli/version/index.js +11 -0
- package/dist/cli/version/index.js.map +1 -0
- package/dist/cli/version/parser.d.ts +38 -0
- package/dist/cli/version/parser.d.ts.map +1 -0
- package/dist/cli/version/parser.js +90 -0
- package/dist/cli/version/parser.js.map +1 -0
- package/dist/cli/version/prompt.d.ts +40 -0
- package/dist/cli/version/prompt.d.ts.map +1 -0
- package/dist/cli/version/prompt.js +162 -0
- package/dist/cli/version/prompt.js.map +1 -0
- package/dist/cli/version/types.d.ts +89 -0
- package/dist/cli/version/types.d.ts.map +1 -0
- package/dist/cli/version/types.js +7 -0
- package/dist/cli/version/types.js.map +1 -0
- package/dist/db/client.d.ts +79 -4
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +207 -12
- package/dist/db/client.js.map +1 -1
- package/dist/db/migrations/files/0014_add_schema_migrations_table.d.ts +14 -0
- package/dist/db/migrations/files/0014_add_schema_migrations_table.d.ts.map +1 -0
- package/dist/db/migrations/files/0014_add_schema_migrations_table.js +25 -0
- package/dist/db/migrations/files/0014_add_schema_migrations_table.js.map +1 -0
- package/dist/db/migrations/index.d.ts +9 -0
- package/dist/db/migrations/index.d.ts.map +1 -0
- package/dist/db/migrations/index.js +9 -0
- package/dist/db/migrations/index.js.map +1 -0
- package/dist/db/migrations/loader.d.ts +27 -0
- package/dist/db/migrations/loader.d.ts.map +1 -0
- package/dist/db/migrations/loader.js +106 -0
- package/dist/db/migrations/loader.js.map +1 -0
- package/dist/db/migrations/runner.d.ts +56 -0
- package/dist/db/migrations/runner.d.ts.map +1 -0
- package/dist/db/migrations/runner.js +266 -0
- package/dist/db/migrations/runner.js.map +1 -0
- package/dist/db/migrations/types.d.ts +71 -0
- package/dist/db/migrations/types.d.ts.map +1 -0
- package/dist/db/migrations/types.js +7 -0
- package/dist/db/migrations/types.js.map +1 -0
- package/dist/db/schema.d.ts +41 -2
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +77 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/mcp.js +2 -2
- package/dist/mcp.js.map +1 -1
- package/dist/template-engine/data-transformer.d.ts +17 -0
- package/dist/template-engine/data-transformer.d.ts.map +1 -0
- package/dist/template-engine/data-transformer.js +343 -0
- package/dist/template-engine/data-transformer.js.map +1 -0
- package/dist/template-engine/engine.d.ts +74 -0
- package/dist/template-engine/engine.d.ts.map +1 -0
- package/dist/template-engine/engine.js +183 -0
- package/dist/template-engine/engine.js.map +1 -0
- package/dist/template-engine/helpers.d.ts +81 -0
- package/dist/template-engine/helpers.d.ts.map +1 -0
- package/dist/template-engine/helpers.js +153 -0
- package/dist/template-engine/helpers.js.map +1 -0
- package/dist/template-engine/index.d.ts +10 -0
- package/dist/template-engine/index.d.ts.map +1 -0
- package/dist/template-engine/index.js +10 -0
- package/dist/template-engine/index.js.map +1 -0
- package/dist/template-engine/types.d.ts +147 -0
- package/dist/template-engine/types.d.ts.map +1 -0
- package/dist/template-engine/types.js +7 -0
- package/dist/template-engine/types.js.map +1 -0
- package/dist/template-sync/comparator.d.ts +138 -0
- package/dist/template-sync/comparator.d.ts.map +1 -0
- package/dist/template-sync/comparator.js +353 -0
- package/dist/template-sync/comparator.js.map +1 -0
- package/dist/template-sync/conflict-resolver.d.ts +112 -0
- package/dist/template-sync/conflict-resolver.d.ts.map +1 -0
- package/dist/template-sync/conflict-resolver.js +328 -0
- package/dist/template-sync/conflict-resolver.js.map +1 -0
- package/dist/template-sync/engine.d.ts +93 -0
- package/dist/template-sync/engine.d.ts.map +1 -0
- package/dist/template-sync/engine.js +350 -0
- package/dist/template-sync/engine.js.map +1 -0
- package/dist/template-sync/hasher.d.ts +67 -0
- package/dist/template-sync/hasher.d.ts.map +1 -0
- package/dist/template-sync/hasher.js +94 -0
- package/dist/template-sync/hasher.js.map +1 -0
- package/dist/template-sync/index.d.ts +20 -0
- package/dist/template-sync/index.d.ts.map +1 -0
- package/dist/template-sync/index.js +14 -0
- package/dist/template-sync/index.js.map +1 -0
- package/dist/template-sync/manifest.d.ts +131 -0
- package/dist/template-sync/manifest.d.ts.map +1 -0
- package/dist/template-sync/manifest.js +309 -0
- package/dist/template-sync/manifest.js.map +1 -0
- package/dist/template-sync/merger.d.ts +125 -0
- package/dist/template-sync/merger.d.ts.map +1 -0
- package/dist/template-sync/merger.js +371 -0
- package/dist/template-sync/merger.js.map +1 -0
- package/dist/template-sync/scanner.d.ts +106 -0
- package/dist/template-sync/scanner.d.ts.map +1 -0
- package/dist/template-sync/scanner.js +196 -0
- package/dist/template-sync/scanner.js.map +1 -0
- package/dist/template-sync/types.d.ts +199 -0
- package/dist/template-sync/types.d.ts.map +1 -0
- package/dist/template-sync/types.js +30 -0
- package/dist/template-sync/types.js.map +1 -0
- package/package.json +2 -1
- package/src/agents/cleanup-agent.ts +21 -6
- package/src/agents/drift-agent.ts +31 -8
- package/src/cli/commands/cleanup.ts +9 -1
- package/src/cli/commands/drift-detect.ts +24 -1
- package/src/cli/commands/embeddings-refresh.ts +135 -0
- package/src/cli/commands/migrate.ts +231 -0
- package/src/cli/commands/restore.ts +318 -0
- package/src/cli/commands/sync-templates.ts +210 -0
- package/src/cli/commands/version-check.ts +158 -0
- package/src/cli/generate.ts +99 -17
- package/src/cli/index.ts +246 -1
- package/src/cli/repl/index.ts +16 -6
- package/src/cli/utils/backup-manager.ts +275 -0
- package/src/cli/utils/db-backup-manager.ts +146 -0
- package/src/cli/utils/file-detector.ts +181 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/cli/utils/modification-prompt.ts +112 -0
- package/src/cli/version/checker.ts +172 -0
- package/src/cli/version/comparator.ts +106 -0
- package/src/cli/version/index.ts +11 -0
- package/src/cli/version/parser.ts +101 -0
- package/src/cli/version/prompt.ts +208 -0
- package/src/cli/version/types.ts +95 -0
- package/src/db/client.ts +285 -18
- package/src/db/migrations/files/0014_add_schema_migrations_table.sql +19 -0
- package/src/db/migrations/files/0014_add_schema_migrations_table.ts +30 -0
- package/src/db/migrations/index.ts +9 -0
- package/src/db/migrations/loader.ts +129 -0
- package/src/db/migrations/runner.ts +316 -0
- package/src/db/migrations/types.ts +71 -0
- package/src/db/schema.ts +109 -2
- package/src/mcp.ts +2 -2
- package/src/template-engine/data-transformer.ts +367 -0
- package/src/template-engine/engine.ts +213 -0
- package/src/template-engine/helpers.ts +163 -0
- package/src/template-engine/index.ts +10 -0
- package/src/template-engine/types.ts +158 -0
- package/src/template-sync/comparator.ts +452 -0
- package/src/template-sync/conflict-resolver.ts +401 -0
- package/src/template-sync/engine.ts +417 -0
- package/src/template-sync/hasher.ts +104 -0
- package/src/template-sync/index.ts +60 -0
- package/src/template-sync/manifest.ts +358 -0
- package/src/template-sync/merger.ts +454 -0
- package/src/template-sync/scanner.ts +254 -0
- package/src/template-sync/types.ts +247 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrate Command
|
|
3
|
+
*
|
|
4
|
+
* Database schema migration command with interactive prompts.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import ora from 'ora';
|
|
12
|
+
import { confirm, select } from '@inquirer/prompts';
|
|
13
|
+
import { DatabaseClient } from '../../db/client.js';
|
|
14
|
+
import { MigrationRunner } from '../../db/migrations/index.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Main migrate command
|
|
18
|
+
*/
|
|
19
|
+
export const migrateCommand = new Command('migrate')
|
|
20
|
+
.description('Manage database schema migrations')
|
|
21
|
+
|
|
22
|
+
// Status subcommand
|
|
23
|
+
.command('status')
|
|
24
|
+
.description('Show migration status')
|
|
25
|
+
.action(async () => {
|
|
26
|
+
const spinner = ora();
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const db = await DatabaseClient.create(process.cwd());
|
|
30
|
+
const runner = new MigrationRunner(db, process.cwd());
|
|
31
|
+
|
|
32
|
+
const status = await runner.getStatus();
|
|
33
|
+
|
|
34
|
+
spinner.stop();
|
|
35
|
+
|
|
36
|
+
console.log(chalk.bold('\nMigration Status:\n'));
|
|
37
|
+
console.log(` Current: ${chalk.cyan(status.currentVersion || 'none')}`);
|
|
38
|
+
console.log(` Target: ${chalk.cyan(status.targetVersion)}`);
|
|
39
|
+
console.log(` Pending: ${chalk.yellow(status.pending.length)} migration(s)\n`);
|
|
40
|
+
|
|
41
|
+
if (status.pending.length > 0) {
|
|
42
|
+
console.log(chalk.bold('Pending Migrations:'));
|
|
43
|
+
for (const migration of status.pending) {
|
|
44
|
+
const breaks = migration.breaks ? chalk.red(' [breaking]') : '';
|
|
45
|
+
console.log(` ${chalk.cyan(migration.version)}: ${migration.description}${breaks}`);
|
|
46
|
+
}
|
|
47
|
+
console.log('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (status.applied.length > 0) {
|
|
51
|
+
console.log(chalk.bold('Applied Migrations:'));
|
|
52
|
+
for (const applied of status.applied.slice(0, 5)) {
|
|
53
|
+
console.log(chalk.dim(` ${applied.version} (${applied.appliedAt})`));
|
|
54
|
+
}
|
|
55
|
+
if (status.applied.length > 5) {
|
|
56
|
+
console.log(chalk.dim(` ... and ${status.applied.length - 5} more`));
|
|
57
|
+
}
|
|
58
|
+
console.log('');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
db.close();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
spinner.fail('Status check failed');
|
|
64
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Up subcommand
|
|
70
|
+
.command('up')
|
|
71
|
+
.description('Apply pending migrations')
|
|
72
|
+
.option('--dry-run', 'Show what would be done')
|
|
73
|
+
.option('--force', 'Apply even if validation fails')
|
|
74
|
+
.option('--no-backup', 'Skip creating backup')
|
|
75
|
+
.action(async (options) => {
|
|
76
|
+
const spinner = ora();
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const db = await DatabaseClient.create(process.cwd());
|
|
80
|
+
const runner = new MigrationRunner(db, process.cwd());
|
|
81
|
+
|
|
82
|
+
const status = await runner.getStatus();
|
|
83
|
+
|
|
84
|
+
if (!status.needsMigration) {
|
|
85
|
+
spinner.succeed('Database is up to date');
|
|
86
|
+
db.close();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check for breaking changes
|
|
91
|
+
const breaking = status.pending.filter(m => m.breaks);
|
|
92
|
+
if (breaking.length > 0 && !options.force) {
|
|
93
|
+
console.log(chalk.yellow(`\n⚠ ${breaking.length} breaking change(s) detected:\n`));
|
|
94
|
+
for (const m of breaking) {
|
|
95
|
+
console.log(chalk.red(` ${m.version}: ${m.description}`));
|
|
96
|
+
}
|
|
97
|
+
console.log();
|
|
98
|
+
|
|
99
|
+
const shouldContinue = await confirm({
|
|
100
|
+
message: 'Continue with breaking changes?',
|
|
101
|
+
default: false
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!shouldContinue) {
|
|
105
|
+
console.log(chalk.dim('\nMigration cancelled.\n'));
|
|
106
|
+
db.close();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Summary
|
|
112
|
+
console.log(chalk.bold(`\nApplying ${status.pending.length} migration(s):\n`));
|
|
113
|
+
for (const migration of status.pending) {
|
|
114
|
+
console.log(chalk.dim(` ${migration.version}: ${migration.description}`));
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
117
|
+
|
|
118
|
+
const confirmed = options.dryRun || await confirm({
|
|
119
|
+
message: 'Proceed?',
|
|
120
|
+
default: true
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!confirmed) {
|
|
124
|
+
console.log(chalk.dim('\nMigration cancelled.\n'));
|
|
125
|
+
db.close();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
spinner.start('Applying migrations...');
|
|
130
|
+
|
|
131
|
+
const results = await runner.migrate({
|
|
132
|
+
dryRun: options.dryRun,
|
|
133
|
+
force: options.force,
|
|
134
|
+
backup: options.backup !== false,
|
|
135
|
+
onProgress: (current, total, migration) => {
|
|
136
|
+
spinner.text = `Applying ${current}/${total}: ${migration.description}`;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
spinner.stop();
|
|
141
|
+
|
|
142
|
+
// Show results
|
|
143
|
+
const successful = results.filter(r => r.success);
|
|
144
|
+
const failed = results.filter(r => !r.success);
|
|
145
|
+
|
|
146
|
+
if (successful.length > 0) {
|
|
147
|
+
console.log(chalk.green(`\n✓ Applied ${successful.length} migration(s)`));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (failed.length > 0) {
|
|
151
|
+
console.log(chalk.red(`\n✖ ${failed.length} migration(s) failed:\n`));
|
|
152
|
+
for (const result of failed) {
|
|
153
|
+
console.log(chalk.red(` ${result.version}: ${result.error}`));
|
|
154
|
+
}
|
|
155
|
+
console.log(chalk.dim(`\nRollback using: k0ntext migrate rollback`));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
db.close();
|
|
159
|
+
} catch (error) {
|
|
160
|
+
spinner.fail('Migration failed');
|
|
161
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Rollback subcommand
|
|
167
|
+
.command('rollback')
|
|
168
|
+
.description('Rollback to a previous backup')
|
|
169
|
+
.option('--backup <path>', 'Specific backup to restore')
|
|
170
|
+
.action(async (options) => {
|
|
171
|
+
const spinner = ora();
|
|
172
|
+
const projectRoot = process.cwd();
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const db = await DatabaseClient.create(projectRoot);
|
|
176
|
+
const runner = new MigrationRunner(db, projectRoot);
|
|
177
|
+
|
|
178
|
+
const backups = await runner.getMigrationBackups();
|
|
179
|
+
|
|
180
|
+
if (backups.length === 0) {
|
|
181
|
+
console.log(chalk.yellow('\nNo migration backups found.\n'));
|
|
182
|
+
db.close();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let selectedBackup: string;
|
|
187
|
+
|
|
188
|
+
if (options.backup) {
|
|
189
|
+
selectedBackup = options.backup;
|
|
190
|
+
} else {
|
|
191
|
+
spinner.stop();
|
|
192
|
+
selectedBackup = await select({
|
|
193
|
+
message: 'Select backup to restore:',
|
|
194
|
+
choices: backups.map(b => ({
|
|
195
|
+
name: b.replace('.k0ntext.db.pre-', '').replace('.bak', ''),
|
|
196
|
+
value: b
|
|
197
|
+
}))
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const confirmed = await confirm({
|
|
202
|
+
message: 'This will replace your current database. Continue?',
|
|
203
|
+
default: false
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (!confirmed) {
|
|
207
|
+
console.log(chalk.dim('\nRollback cancelled.\n'));
|
|
208
|
+
db.close();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
spinner.start('Restoring backup...');
|
|
213
|
+
|
|
214
|
+
// Close database first
|
|
215
|
+
db.close();
|
|
216
|
+
|
|
217
|
+
// Restore from backup
|
|
218
|
+
const backupPath = path.join(projectRoot, '.k0ntext', 'backups', selectedBackup);
|
|
219
|
+
const dbPath = path.join(projectRoot, '.k0ntext.db');
|
|
220
|
+
|
|
221
|
+
await fs.copyFile(backupPath, dbPath);
|
|
222
|
+
|
|
223
|
+
spinner.succeed(chalk.green('Database restored from backup'));
|
|
224
|
+
console.log(chalk.dim(`\nBackup: ${selectedBackup}\n`));
|
|
225
|
+
|
|
226
|
+
} catch (error) {
|
|
227
|
+
spinner.fail('Rollback failed');
|
|
228
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restore Command
|
|
3
|
+
*
|
|
4
|
+
* CLI command to restore AI tool config files from backups.
|
|
5
|
+
* Supports listing backups and restoring from specific backups.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import ora from 'ora';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs/promises';
|
|
13
|
+
import { select, confirm } from '@inquirer/prompts';
|
|
14
|
+
import type { DatabaseClient } from '../../db/client.js';
|
|
15
|
+
import { BackupManager } from '../utils/backup-manager.js';
|
|
16
|
+
import type { BackupResult } from '../utils/backup-manager.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse git stash reference from backup path
|
|
20
|
+
*/
|
|
21
|
+
function parseGitStashRef(backupPath: string): { stashRef: string } | null {
|
|
22
|
+
if (backupPath.startsWith('git-stash:')) {
|
|
23
|
+
return { stashRef: backupPath.replace('git-stash:', '') };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Restore from git stash
|
|
30
|
+
*/
|
|
31
|
+
async function restoreFromGitStash(stashRef: string, projectRoot: string, filePath: string): Promise<boolean> {
|
|
32
|
+
try {
|
|
33
|
+
const { execSync } = await import('child_process');
|
|
34
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
35
|
+
|
|
36
|
+
// Try to apply the stash
|
|
37
|
+
execSync(`git stash apply ${stashRef} -- "${relativePath}"`, {
|
|
38
|
+
cwd: projectRoot,
|
|
39
|
+
stdio: 'pipe'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Format backup info for display
|
|
50
|
+
*/
|
|
51
|
+
function formatBackupInfo(backupPath: string, tool: string): string {
|
|
52
|
+
const gitStash = parseGitStashRef(backupPath);
|
|
53
|
+
|
|
54
|
+
if (gitStash) {
|
|
55
|
+
return `${chalk.cyan(tool.padEnd(12))} ${chalk.dim('Git stash:')} ${chalk.yellow(gitStash.stashRef)}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fileName = path.basename(backupPath);
|
|
59
|
+
const dateMatch = fileName.match(/\.(\d{4}-\d{2}-\d{2}T[\d:.-]+)\.bak$/);
|
|
60
|
+
|
|
61
|
+
if (dateMatch) {
|
|
62
|
+
const date = new Date(dateMatch[1].replace(/-/g, ':'));
|
|
63
|
+
const formatted = date.toLocaleString();
|
|
64
|
+
return `${chalk.cyan(tool.padEnd(12))} ${chalk.dim('File:')} ${chalk.yellow(fileName)} ${chalk.dim(`(${formatted})`)}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return `${chalk.cyan(tool.padEnd(12))} ${chalk.yellow(backupPath)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* List available backups for a tool
|
|
72
|
+
*/
|
|
73
|
+
async function listBackupsForTool(
|
|
74
|
+
db: DatabaseClient,
|
|
75
|
+
projectRoot: string,
|
|
76
|
+
tool: string
|
|
77
|
+
): Promise<Array<{ tool: string; backupPath: string; filePath: string; generatedAt?: string }>> {
|
|
78
|
+
const generatedFiles = db.getGeneratedFiles(tool);
|
|
79
|
+
|
|
80
|
+
return generatedFiles
|
|
81
|
+
.filter(f => f.backupPath)
|
|
82
|
+
.map(f => ({
|
|
83
|
+
tool,
|
|
84
|
+
backupPath: f.backupPath!,
|
|
85
|
+
filePath: f.filePath,
|
|
86
|
+
generatedAt: f.generatedAt
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Restore command
|
|
92
|
+
*/
|
|
93
|
+
export const restoreCommand = new Command('restore')
|
|
94
|
+
.description('Restore AI tool config files from backups')
|
|
95
|
+
.option('--list', 'List available backups')
|
|
96
|
+
.option('--backup <path>', 'Restore from specific backup path')
|
|
97
|
+
.option('--tool <name>', 'Filter by tool name')
|
|
98
|
+
.option('--force', 'Restore without confirmation')
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
const spinner = ora();
|
|
101
|
+
const projectRoot = process.cwd();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Load database
|
|
105
|
+
const { DatabaseClient } = await import('../../db/client.js');
|
|
106
|
+
const db = new DatabaseClient(projectRoot);
|
|
107
|
+
const backupManager = new BackupManager(db, projectRoot);
|
|
108
|
+
|
|
109
|
+
// List mode
|
|
110
|
+
if (options.list) {
|
|
111
|
+
spinner.start('Finding backups...');
|
|
112
|
+
|
|
113
|
+
const tools = options.tool
|
|
114
|
+
? [options.tool]
|
|
115
|
+
: ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue', 'cursor', 'gemini'];
|
|
116
|
+
|
|
117
|
+
const allBackups: Array<{ tool: string; backupPath: string; filePath: string }> = [];
|
|
118
|
+
|
|
119
|
+
for (const tool of tools) {
|
|
120
|
+
const backups = await listBackupsForTool(db, projectRoot, tool);
|
|
121
|
+
allBackups.push(...backups);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
spinner.stop();
|
|
125
|
+
|
|
126
|
+
if (allBackups.length === 0) {
|
|
127
|
+
console.log(chalk.yellow('\nNo backups found.'));
|
|
128
|
+
console.log(chalk.dim('Generate context files first to create backups.\n'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(chalk.bold('\nAvailable Backups'));
|
|
133
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
134
|
+
|
|
135
|
+
for (const backup of allBackups) {
|
|
136
|
+
console.log(formatBackupInfo(backup.backupPath, backup.tool));
|
|
137
|
+
console.log(chalk.dim(` Target: ${backup.filePath}\n`));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(chalk.dim(`Run ${chalk.white('k0ntext restore --backup <path>')} to restore from a backup.\n`));
|
|
141
|
+
db.close();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Restore from specific backup
|
|
146
|
+
if (options.backup) {
|
|
147
|
+
const backupPath = options.backup;
|
|
148
|
+
const targetPath = await findTargetForBackup(db, backupPath, options.tool);
|
|
149
|
+
|
|
150
|
+
if (!targetPath) {
|
|
151
|
+
spinner.fail(chalk.red('Backup not found in database'));
|
|
152
|
+
console.log(chalk.dim('\nUse --list to see available backups.\n'));
|
|
153
|
+
db.close();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Confirm unless --force
|
|
158
|
+
if (!options.force) {
|
|
159
|
+
const confirmed = await confirm({
|
|
160
|
+
message: `Restore ${targetPath.filePath} from backup?`,
|
|
161
|
+
default: false
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!confirmed) {
|
|
165
|
+
console.log(chalk.dim('\nRestore cancelled.\n'));
|
|
166
|
+
db.close();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
spinner.start('Restoring from backup...');
|
|
172
|
+
|
|
173
|
+
const success = await restoreFromBackup(backupPath, targetPath.filePath, projectRoot);
|
|
174
|
+
|
|
175
|
+
if (success) {
|
|
176
|
+
spinner.succeed(chalk.green('File restored successfully!'));
|
|
177
|
+
|
|
178
|
+
// Clear the user_modified flag since we restored the generated version
|
|
179
|
+
const tool = targetPath.tool;
|
|
180
|
+
db.upsertGeneratedFile({
|
|
181
|
+
tool,
|
|
182
|
+
filePath: targetPath.filePath,
|
|
183
|
+
contentHash: db.hashContent(await fs.readFile(targetPath.filePath, 'utf-8')),
|
|
184
|
+
backupPath: undefined // Clear backup after restore
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
spinner.fail(chalk.red('Restore failed'));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
db.close();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Interactive mode
|
|
195
|
+
spinner.start('Loading backups...');
|
|
196
|
+
|
|
197
|
+
const tools = ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue', 'cursor', 'gemini'];
|
|
198
|
+
const allBackups: Array<{ tool: string; backupPath: string; filePath: string; label: string }> = [];
|
|
199
|
+
|
|
200
|
+
for (const tool of tools) {
|
|
201
|
+
const backups = await listBackupsForTool(db, projectRoot, tool);
|
|
202
|
+
for (const backup of backups) {
|
|
203
|
+
allBackups.push({
|
|
204
|
+
...backup,
|
|
205
|
+
label: `${tool}: ${path.basename(backup.filePath)} (${backup.backupPath.split(':').pop()})`
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
spinner.stop();
|
|
211
|
+
|
|
212
|
+
if (allBackups.length === 0) {
|
|
213
|
+
console.log(chalk.yellow('\nNo backups found.'));
|
|
214
|
+
console.log(chalk.dim('Generate context files first to create backups.\n'));
|
|
215
|
+
db.close();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Prompt user to select backup
|
|
220
|
+
const selected = await select({
|
|
221
|
+
message: 'Select a backup to restore:',
|
|
222
|
+
choices: [
|
|
223
|
+
...allBackups.map(b => ({
|
|
224
|
+
name: b.label,
|
|
225
|
+
value: b
|
|
226
|
+
})),
|
|
227
|
+
{ name: 'Cancel', value: null }
|
|
228
|
+
]
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (!selected) {
|
|
232
|
+
console.log(chalk.dim('\nRestore cancelled.\n'));
|
|
233
|
+
db.close();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Confirm restore
|
|
238
|
+
const confirmed = await confirm({
|
|
239
|
+
message: `Restore ${selected.filePath} from backup?`,
|
|
240
|
+
default: true
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (!confirmed) {
|
|
244
|
+
console.log(chalk.dim('\nRestore cancelled.\n'));
|
|
245
|
+
db.close();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
spinner.start('Restoring from backup...');
|
|
250
|
+
|
|
251
|
+
const success = await restoreFromBackup(selected.backupPath, selected.filePath, projectRoot);
|
|
252
|
+
|
|
253
|
+
if (success) {
|
|
254
|
+
spinner.succeed(chalk.green('File restored successfully!'));
|
|
255
|
+
|
|
256
|
+
// Clear the user_modified flag
|
|
257
|
+
const content = await fs.readFile(selected.filePath, 'utf-8');
|
|
258
|
+
db.upsertGeneratedFile({
|
|
259
|
+
tool: selected.tool,
|
|
260
|
+
filePath: selected.filePath,
|
|
261
|
+
contentHash: db.hashContent(content),
|
|
262
|
+
backupPath: undefined
|
|
263
|
+
});
|
|
264
|
+
} else {
|
|
265
|
+
spinner.fail(chalk.red('Restore failed'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
db.close();
|
|
269
|
+
|
|
270
|
+
} catch (error) {
|
|
271
|
+
spinner.fail('Restore failed');
|
|
272
|
+
console.error(chalk.red(`\nError: ${error instanceof Error ? error.message : error}`));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Find the target file path for a backup
|
|
279
|
+
*/
|
|
280
|
+
async function findTargetForBackup(
|
|
281
|
+
db: DatabaseClient,
|
|
282
|
+
backupPath: string,
|
|
283
|
+
toolFilter?: string
|
|
284
|
+
): Promise<{ tool: string; filePath: string } | null> {
|
|
285
|
+
const tools = toolFilter
|
|
286
|
+
? [toolFilter]
|
|
287
|
+
: ['claude', 'copilot', 'cline', 'antigravity', 'windsurf', 'aider', 'continue', 'cursor', 'gemini'];
|
|
288
|
+
|
|
289
|
+
for (const tool of tools) {
|
|
290
|
+
const files = db.getGeneratedFiles(tool);
|
|
291
|
+
for (const file of files) {
|
|
292
|
+
if (file.backupPath === backupPath) {
|
|
293
|
+
return { tool, filePath: file.filePath };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Restore from a backup (file or git stash)
|
|
303
|
+
*/
|
|
304
|
+
async function restoreFromBackup(backupPath: string, targetPath: string, projectRoot: string): Promise<boolean> {
|
|
305
|
+
const gitStash = parseGitStashRef(backupPath);
|
|
306
|
+
|
|
307
|
+
if (gitStash) {
|
|
308
|
+
return await restoreFromGitStash(gitStash.stashRef, projectRoot, targetPath);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// File copy restore
|
|
312
|
+
try {
|
|
313
|
+
await fs.copyFile(backupPath, targetPath);
|
|
314
|
+
return true;
|
|
315
|
+
} catch {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|