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.
Files changed (221) hide show
  1. package/README.md +225 -26
  2. package/dist/agents/cleanup-agent.d.ts.map +1 -1
  3. package/dist/agents/cleanup-agent.js +18 -6
  4. package/dist/agents/cleanup-agent.js.map +1 -1
  5. package/dist/agents/drift-agent.d.ts +7 -0
  6. package/dist/agents/drift-agent.d.ts.map +1 -1
  7. package/dist/agents/drift-agent.js +29 -8
  8. package/dist/agents/drift-agent.js.map +1 -1
  9. package/dist/cli/commands/cleanup.d.ts.map +1 -1
  10. package/dist/cli/commands/cleanup.js +8 -1
  11. package/dist/cli/commands/cleanup.js.map +1 -1
  12. package/dist/cli/commands/drift-detect.d.ts.map +1 -1
  13. package/dist/cli/commands/drift-detect.js +21 -1
  14. package/dist/cli/commands/drift-detect.js.map +1 -1
  15. package/dist/cli/commands/embeddings-refresh.d.ts +11 -0
  16. package/dist/cli/commands/embeddings-refresh.d.ts.map +1 -0
  17. package/dist/cli/commands/embeddings-refresh.js +114 -0
  18. package/dist/cli/commands/embeddings-refresh.js.map +1 -0
  19. package/dist/cli/commands/migrate.d.ts +11 -0
  20. package/dist/cli/commands/migrate.d.ts.map +1 -0
  21. package/dist/cli/commands/migrate.js +195 -0
  22. package/dist/cli/commands/migrate.js.map +1 -0
  23. package/dist/cli/commands/restore.d.ts +12 -0
  24. package/dist/cli/commands/restore.d.ts.map +1 -0
  25. package/dist/cli/commands/restore.js +261 -0
  26. package/dist/cli/commands/restore.js.map +1 -0
  27. package/dist/cli/commands/sync-templates.d.ts +15 -0
  28. package/dist/cli/commands/sync-templates.d.ts.map +1 -0
  29. package/dist/cli/commands/sync-templates.js +181 -0
  30. package/dist/cli/commands/sync-templates.js.map +1 -0
  31. package/dist/cli/commands/version-check.d.ts +12 -0
  32. package/dist/cli/commands/version-check.d.ts.map +1 -0
  33. package/dist/cli/commands/version-check.js +133 -0
  34. package/dist/cli/commands/version-check.js.map +1 -0
  35. package/dist/cli/generate.d.ts +5 -0
  36. package/dist/cli/generate.d.ts.map +1 -1
  37. package/dist/cli/generate.js +80 -16
  38. package/dist/cli/generate.js.map +1 -1
  39. package/dist/cli/index.js +215 -1
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/repl/index.d.ts +1 -0
  42. package/dist/cli/repl/index.d.ts.map +1 -1
  43. package/dist/cli/repl/index.js +18 -6
  44. package/dist/cli/repl/index.js.map +1 -1
  45. package/dist/cli/utils/backup-manager.d.ts +94 -0
  46. package/dist/cli/utils/backup-manager.d.ts.map +1 -0
  47. package/dist/cli/utils/backup-manager.js +230 -0
  48. package/dist/cli/utils/backup-manager.js.map +1 -0
  49. package/dist/cli/utils/db-backup-manager.d.ts +55 -0
  50. package/dist/cli/utils/db-backup-manager.d.ts.map +1 -0
  51. package/dist/cli/utils/db-backup-manager.js +115 -0
  52. package/dist/cli/utils/db-backup-manager.js.map +1 -0
  53. package/dist/cli/utils/file-detector.d.ts +87 -0
  54. package/dist/cli/utils/file-detector.d.ts.map +1 -0
  55. package/dist/cli/utils/file-detector.js +131 -0
  56. package/dist/cli/utils/file-detector.js.map +1 -0
  57. package/dist/cli/utils/index.d.ts +9 -0
  58. package/dist/cli/utils/index.d.ts.map +1 -0
  59. package/dist/cli/utils/index.js +9 -0
  60. package/dist/cli/utils/index.js.map +1 -0
  61. package/dist/cli/utils/modification-prompt.d.ts +41 -0
  62. package/dist/cli/utils/modification-prompt.d.ts.map +1 -0
  63. package/dist/cli/utils/modification-prompt.js +84 -0
  64. package/dist/cli/utils/modification-prompt.js.map +1 -0
  65. package/dist/cli/version/checker.d.ts +47 -0
  66. package/dist/cli/version/checker.d.ts.map +1 -0
  67. package/dist/cli/version/checker.js +143 -0
  68. package/dist/cli/version/checker.js.map +1 -0
  69. package/dist/cli/version/comparator.d.ts +46 -0
  70. package/dist/cli/version/comparator.d.ts.map +1 -0
  71. package/dist/cli/version/comparator.js +99 -0
  72. package/dist/cli/version/comparator.js.map +1 -0
  73. package/dist/cli/version/index.d.ts +11 -0
  74. package/dist/cli/version/index.d.ts.map +1 -0
  75. package/dist/cli/version/index.js +11 -0
  76. package/dist/cli/version/index.js.map +1 -0
  77. package/dist/cli/version/parser.d.ts +38 -0
  78. package/dist/cli/version/parser.d.ts.map +1 -0
  79. package/dist/cli/version/parser.js +90 -0
  80. package/dist/cli/version/parser.js.map +1 -0
  81. package/dist/cli/version/prompt.d.ts +40 -0
  82. package/dist/cli/version/prompt.d.ts.map +1 -0
  83. package/dist/cli/version/prompt.js +162 -0
  84. package/dist/cli/version/prompt.js.map +1 -0
  85. package/dist/cli/version/types.d.ts +89 -0
  86. package/dist/cli/version/types.d.ts.map +1 -0
  87. package/dist/cli/version/types.js +7 -0
  88. package/dist/cli/version/types.js.map +1 -0
  89. package/dist/db/client.d.ts +79 -4
  90. package/dist/db/client.d.ts.map +1 -1
  91. package/dist/db/client.js +207 -12
  92. package/dist/db/client.js.map +1 -1
  93. package/dist/db/migrations/files/0014_add_schema_migrations_table.d.ts +14 -0
  94. package/dist/db/migrations/files/0014_add_schema_migrations_table.d.ts.map +1 -0
  95. package/dist/db/migrations/files/0014_add_schema_migrations_table.js +25 -0
  96. package/dist/db/migrations/files/0014_add_schema_migrations_table.js.map +1 -0
  97. package/dist/db/migrations/index.d.ts +9 -0
  98. package/dist/db/migrations/index.d.ts.map +1 -0
  99. package/dist/db/migrations/index.js +9 -0
  100. package/dist/db/migrations/index.js.map +1 -0
  101. package/dist/db/migrations/loader.d.ts +27 -0
  102. package/dist/db/migrations/loader.d.ts.map +1 -0
  103. package/dist/db/migrations/loader.js +106 -0
  104. package/dist/db/migrations/loader.js.map +1 -0
  105. package/dist/db/migrations/runner.d.ts +56 -0
  106. package/dist/db/migrations/runner.d.ts.map +1 -0
  107. package/dist/db/migrations/runner.js +266 -0
  108. package/dist/db/migrations/runner.js.map +1 -0
  109. package/dist/db/migrations/types.d.ts +71 -0
  110. package/dist/db/migrations/types.d.ts.map +1 -0
  111. package/dist/db/migrations/types.js +7 -0
  112. package/dist/db/migrations/types.js.map +1 -0
  113. package/dist/db/schema.d.ts +41 -2
  114. package/dist/db/schema.d.ts.map +1 -1
  115. package/dist/db/schema.js +77 -2
  116. package/dist/db/schema.js.map +1 -1
  117. package/dist/mcp.js +2 -2
  118. package/dist/mcp.js.map +1 -1
  119. package/dist/template-engine/data-transformer.d.ts +17 -0
  120. package/dist/template-engine/data-transformer.d.ts.map +1 -0
  121. package/dist/template-engine/data-transformer.js +343 -0
  122. package/dist/template-engine/data-transformer.js.map +1 -0
  123. package/dist/template-engine/engine.d.ts +74 -0
  124. package/dist/template-engine/engine.d.ts.map +1 -0
  125. package/dist/template-engine/engine.js +183 -0
  126. package/dist/template-engine/engine.js.map +1 -0
  127. package/dist/template-engine/helpers.d.ts +81 -0
  128. package/dist/template-engine/helpers.d.ts.map +1 -0
  129. package/dist/template-engine/helpers.js +153 -0
  130. package/dist/template-engine/helpers.js.map +1 -0
  131. package/dist/template-engine/index.d.ts +10 -0
  132. package/dist/template-engine/index.d.ts.map +1 -0
  133. package/dist/template-engine/index.js +10 -0
  134. package/dist/template-engine/index.js.map +1 -0
  135. package/dist/template-engine/types.d.ts +147 -0
  136. package/dist/template-engine/types.d.ts.map +1 -0
  137. package/dist/template-engine/types.js +7 -0
  138. package/dist/template-engine/types.js.map +1 -0
  139. package/dist/template-sync/comparator.d.ts +138 -0
  140. package/dist/template-sync/comparator.d.ts.map +1 -0
  141. package/dist/template-sync/comparator.js +353 -0
  142. package/dist/template-sync/comparator.js.map +1 -0
  143. package/dist/template-sync/conflict-resolver.d.ts +112 -0
  144. package/dist/template-sync/conflict-resolver.d.ts.map +1 -0
  145. package/dist/template-sync/conflict-resolver.js +328 -0
  146. package/dist/template-sync/conflict-resolver.js.map +1 -0
  147. package/dist/template-sync/engine.d.ts +93 -0
  148. package/dist/template-sync/engine.d.ts.map +1 -0
  149. package/dist/template-sync/engine.js +350 -0
  150. package/dist/template-sync/engine.js.map +1 -0
  151. package/dist/template-sync/hasher.d.ts +67 -0
  152. package/dist/template-sync/hasher.d.ts.map +1 -0
  153. package/dist/template-sync/hasher.js +94 -0
  154. package/dist/template-sync/hasher.js.map +1 -0
  155. package/dist/template-sync/index.d.ts +20 -0
  156. package/dist/template-sync/index.d.ts.map +1 -0
  157. package/dist/template-sync/index.js +14 -0
  158. package/dist/template-sync/index.js.map +1 -0
  159. package/dist/template-sync/manifest.d.ts +131 -0
  160. package/dist/template-sync/manifest.d.ts.map +1 -0
  161. package/dist/template-sync/manifest.js +309 -0
  162. package/dist/template-sync/manifest.js.map +1 -0
  163. package/dist/template-sync/merger.d.ts +125 -0
  164. package/dist/template-sync/merger.d.ts.map +1 -0
  165. package/dist/template-sync/merger.js +371 -0
  166. package/dist/template-sync/merger.js.map +1 -0
  167. package/dist/template-sync/scanner.d.ts +106 -0
  168. package/dist/template-sync/scanner.d.ts.map +1 -0
  169. package/dist/template-sync/scanner.js +196 -0
  170. package/dist/template-sync/scanner.js.map +1 -0
  171. package/dist/template-sync/types.d.ts +199 -0
  172. package/dist/template-sync/types.d.ts.map +1 -0
  173. package/dist/template-sync/types.js +30 -0
  174. package/dist/template-sync/types.js.map +1 -0
  175. package/package.json +2 -1
  176. package/src/agents/cleanup-agent.ts +21 -6
  177. package/src/agents/drift-agent.ts +31 -8
  178. package/src/cli/commands/cleanup.ts +9 -1
  179. package/src/cli/commands/drift-detect.ts +24 -1
  180. package/src/cli/commands/embeddings-refresh.ts +135 -0
  181. package/src/cli/commands/migrate.ts +231 -0
  182. package/src/cli/commands/restore.ts +318 -0
  183. package/src/cli/commands/sync-templates.ts +210 -0
  184. package/src/cli/commands/version-check.ts +158 -0
  185. package/src/cli/generate.ts +99 -17
  186. package/src/cli/index.ts +246 -1
  187. package/src/cli/repl/index.ts +16 -6
  188. package/src/cli/utils/backup-manager.ts +275 -0
  189. package/src/cli/utils/db-backup-manager.ts +146 -0
  190. package/src/cli/utils/file-detector.ts +181 -0
  191. package/src/cli/utils/index.ts +9 -0
  192. package/src/cli/utils/modification-prompt.ts +112 -0
  193. package/src/cli/version/checker.ts +172 -0
  194. package/src/cli/version/comparator.ts +106 -0
  195. package/src/cli/version/index.ts +11 -0
  196. package/src/cli/version/parser.ts +101 -0
  197. package/src/cli/version/prompt.ts +208 -0
  198. package/src/cli/version/types.ts +95 -0
  199. package/src/db/client.ts +285 -18
  200. package/src/db/migrations/files/0014_add_schema_migrations_table.sql +19 -0
  201. package/src/db/migrations/files/0014_add_schema_migrations_table.ts +30 -0
  202. package/src/db/migrations/index.ts +9 -0
  203. package/src/db/migrations/loader.ts +129 -0
  204. package/src/db/migrations/runner.ts +316 -0
  205. package/src/db/migrations/types.ts +71 -0
  206. package/src/db/schema.ts +109 -2
  207. package/src/mcp.ts +2 -2
  208. package/src/template-engine/data-transformer.ts +367 -0
  209. package/src/template-engine/engine.ts +213 -0
  210. package/src/template-engine/helpers.ts +163 -0
  211. package/src/template-engine/index.ts +10 -0
  212. package/src/template-engine/types.ts +158 -0
  213. package/src/template-sync/comparator.ts +452 -0
  214. package/src/template-sync/conflict-resolver.ts +401 -0
  215. package/src/template-sync/engine.ts +417 -0
  216. package/src/template-sync/hasher.ts +104 -0
  217. package/src/template-sync/index.ts +60 -0
  218. package/src/template-sync/manifest.ts +358 -0
  219. package/src/template-sync/merger.ts +454 -0
  220. package/src/template-sync/scanner.ts +254 -0
  221. 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
+ }