@leejungkiin/awkit 1.6.3 → 1.6.5

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.
@@ -0,0 +1,494 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Obsidian Sync — Exports Symphony task data to Obsidian Markdown notes.
5
+ *
6
+ * Reads task data from Symphony SQLite DB and writes a formatted Markdown
7
+ * section into an Obsidian note file. Uses boundary markers to preserve
8
+ * any user-written content outside the sync region.
9
+ *
10
+ * Config is read from `.project-identity` → `automation.obsidian`.
11
+ *
12
+ * Usage (via awkit CLI):
13
+ * awkit obsidian sync Full sync for current project
14
+ * awkit obsidian sync --all Sync all projects that have obsidian config
15
+ * awkit obsidian status Show sync status (dry-run)
16
+ * awkit obsidian help Show help
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { execSync } = require('child_process');
22
+
23
+ // ─── Constants ────────────────────────────────────────────────────────────────
24
+
25
+ const HOME = process.env.HOME || process.env.USERPROFILE;
26
+ const SYMPHONY_DB = path.join(HOME, '.gemini', 'antigravity', 'symphony', 'symphony.db');
27
+
28
+ // ─── Color helpers ────────────────────────────────────────────────────────────
29
+
30
+ const C = {
31
+ reset: '\x1b[0m',
32
+ red: '\x1b[31m',
33
+ green: '\x1b[32m',
34
+ yellow: '\x1b[33m',
35
+ cyan: '\x1b[36m',
36
+ gray: '\x1b[90m',
37
+ bold: '\x1b[1m',
38
+ };
39
+
40
+ const log = (msg) => console.log(msg);
41
+ const ok = (msg) => log(`${C.green}✔${C.reset} ${msg}`);
42
+ const warn = (msg) => log(`${C.yellow}⚠${C.reset} ${msg}`);
43
+ const err = (msg) => log(`${C.red}✖${C.reset} ${msg}`);
44
+ const info = (msg) => log(`${C.cyan}ℹ${C.reset} ${msg}`);
45
+ const dim = (msg) => log(`${C.gray}${msg}${C.reset}`);
46
+
47
+ // ─── SQLite reader (uses system sqlite3 CLI) ──────────────────────────────────
48
+
49
+ /**
50
+ * Execute a SQLite query via the system `sqlite3` command.
51
+ * Returns an array of objects with column names as keys.
52
+ */
53
+ function querySqlite(dbPath, sql) {
54
+ if (!fs.existsSync(dbPath)) {
55
+ return [];
56
+ }
57
+
58
+ try {
59
+ // Use JSON output mode for reliable parsing
60
+ const raw = execSync(
61
+ `sqlite3 -json "${dbPath}" "${sql.replace(/"/g, '\\"')}"`,
62
+ { encoding: 'utf8', maxBuffer: 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
63
+ );
64
+ const trimmed = raw.trim();
65
+ if (!trimmed || trimmed === '[]') return [];
66
+ return JSON.parse(trimmed);
67
+ } catch (e) {
68
+ // sqlite3 may not support -json on older versions, fallback to CSV
69
+ try {
70
+ const raw = execSync(
71
+ `sqlite3 -header -csv "${dbPath}" "${sql.replace(/"/g, '\\"')}"`,
72
+ { encoding: 'utf8', maxBuffer: 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] }
73
+ );
74
+ return parseCsvOutput(raw.trim());
75
+ } catch (e2) {
76
+ warn(`SQLite query failed: ${e2.message}`);
77
+ return [];
78
+ }
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Parse CSV output from sqlite3 -header -csv into array of objects.
84
+ */
85
+ function parseCsvOutput(csv) {
86
+ if (!csv) return [];
87
+ const lines = csv.split('\n').filter(l => l.trim());
88
+ if (lines.length < 2) return [];
89
+
90
+ const headers = parseCSVLine(lines[0]);
91
+ const rows = [];
92
+
93
+ for (let i = 1; i < lines.length; i++) {
94
+ const values = parseCSVLine(lines[i]);
95
+ const obj = {};
96
+ headers.forEach((h, idx) => {
97
+ obj[h] = values[idx] || '';
98
+ });
99
+ rows.push(obj);
100
+ }
101
+ return rows;
102
+ }
103
+
104
+ /**
105
+ * Simple CSV line parser (handles quoted fields).
106
+ */
107
+ function parseCSVLine(line) {
108
+ const fields = [];
109
+ let current = '';
110
+ let inQuotes = false;
111
+
112
+ for (let i = 0; i < line.length; i++) {
113
+ const ch = line[i];
114
+ if (ch === '"') {
115
+ if (inQuotes && line[i + 1] === '"') {
116
+ current += '"';
117
+ i++;
118
+ } else {
119
+ inQuotes = !inQuotes;
120
+ }
121
+ } else if (ch === ',' && !inQuotes) {
122
+ fields.push(current);
123
+ current = '';
124
+ } else {
125
+ current += ch;
126
+ }
127
+ }
128
+ fields.push(current);
129
+ return fields;
130
+ }
131
+
132
+ // ─── .project-identity reader ─────────────────────────────────────────────────
133
+
134
+ function readProjectIdentity(startDir = process.cwd()) {
135
+ let dir = path.resolve(startDir);
136
+ const root = path.parse(dir).root;
137
+
138
+ while (dir !== root) {
139
+ const candidate = path.join(dir, '.project-identity');
140
+ if (fs.existsSync(candidate)) {
141
+ try {
142
+ return JSON.parse(fs.readFileSync(candidate, 'utf8'));
143
+ } catch (e) {
144
+ return null;
145
+ }
146
+ }
147
+ dir = path.dirname(dir);
148
+ }
149
+ return null;
150
+ }
151
+
152
+ /**
153
+ * Get obsidian config from .project-identity.
154
+ * Returns { enabled, path, template, autoSync } or null.
155
+ */
156
+ function getObsidianConfig(identity) {
157
+ if (!identity) return null;
158
+ const obsidian = identity.automation?.obsidian;
159
+ if (!obsidian || !obsidian.enabled || !obsidian.path) return null;
160
+ return obsidian;
161
+ }
162
+
163
+ // ─── Markdown renderer (Obsidian Kanban format) ──────────────────────────────
164
+
165
+ /**
166
+ * Map priority values to emoji tag for Kanban cards.
167
+ */
168
+ function priorityTag(priority) {
169
+ const map = {
170
+ '0': '🔴', '1': '🟠', '2': '🟡', '3': '🟢',
171
+ 'critical': '🔴', 'high': '🟠', 'medium': '🟡', 'low': '🟢',
172
+ };
173
+ return map[String(priority).toLowerCase()] || '⚪';
174
+ }
175
+
176
+ /**
177
+ * Format a task line for Kanban card.
178
+ * Done tasks get ✅ YYYY-MM-DD suffix (Kanban plugin convention).
179
+ */
180
+ function formatKanbanCard(task) {
181
+ const emoji = priorityTag(task.priority);
182
+ const title = task.title || 'Untitled';
183
+
184
+ if (task.status === 'done') {
185
+ const doneDate = task.completed_at
186
+ ? task.completed_at.substring(0, 10) // Extract YYYY-MM-DD
187
+ : new Date().toISOString().substring(0, 10);
188
+ return `- [x] ${emoji} ${title} ✅ ${doneDate}`;
189
+ }
190
+ return `- [ ] ${emoji} ${title}`;
191
+ }
192
+
193
+ /**
194
+ * Extract existing frontmatter and kanban settings to preserve customizations.
195
+ */
196
+ function extractExistingBlocks(filePath) {
197
+ let result = { frontmatterLines: null, settingsJson: null };
198
+ if (!fs.existsSync(filePath)) return result;
199
+
200
+ try {
201
+ const content = fs.readFileSync(filePath, 'utf8');
202
+
203
+ // Extract Frontmatter
204
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
205
+ if (fmMatch) {
206
+ result.frontmatterLines = fmMatch[1].trim().split('\n');
207
+ }
208
+
209
+ // Extract Kanban Settings
210
+ const setMatch = content.match(/%% kanban:settings\n```.*\n([\s\S]*?)\n```\n%%/);
211
+ if (setMatch) {
212
+ try {
213
+ result.settingsJson = JSON.parse(setMatch[1].trim());
214
+ } catch (e) {}
215
+ }
216
+ } catch (e) {}
217
+
218
+ return result;
219
+ }
220
+
221
+ /**
222
+ * Render full Obsidian Kanban board Markdown.
223
+ * Output is the ENTIRE file content (frontmatter + columns + settings).
224
+ */
225
+ function renderKanbanContent(projectInfo, tasks, existingData = {}) {
226
+ // Group tasks by status
227
+ const inProgress = tasks.filter(t => t.status === 'in_progress' || t.status === 'internal_review');
228
+ const ready = tasks.filter(t => t.status === 'ready');
229
+ const done = tasks.filter(t => t.status === 'done');
230
+ const blocked = tasks.filter(t => t.status === 'blocked');
231
+
232
+ // Count columns that have content (for list-collapse array)
233
+ const columns = [];
234
+
235
+ const lines = [];
236
+
237
+ // ── Frontmatter ──
238
+ lines.push('---');
239
+ if (existingData.frontmatterLines && existingData.frontmatterLines.length > 0) {
240
+ existingData.frontmatterLines.forEach(l => lines.push(l));
241
+ } else {
242
+ lines.push('');
243
+ lines.push('kanban-plugin: board');
244
+ lines.push(`sticker: lucide//list-checks`);
245
+ lines.push('');
246
+ }
247
+ lines.push('---');
248
+ lines.push('');
249
+
250
+ // ── Doing column ──
251
+ lines.push('## Doing');
252
+ lines.push('');
253
+ if (inProgress.length > 0) {
254
+ inProgress
255
+ .sort((a, b) => String(a.priority).localeCompare(String(b.priority)))
256
+ .forEach(t => lines.push(formatKanbanCard(t)));
257
+ }
258
+ lines.push('');
259
+ lines.push('');
260
+ columns.push(false);
261
+
262
+ // ── Blocked column (only if there are blocked tasks) ──
263
+ if (blocked.length > 0) {
264
+ lines.push('## Blocked');
265
+ lines.push('');
266
+ blocked.forEach(t => lines.push(formatKanbanCard(t)));
267
+ lines.push('');
268
+ lines.push('');
269
+ columns.push(false);
270
+ }
271
+
272
+ // ── Todo column ──
273
+ lines.push('## Todo');
274
+ lines.push('');
275
+ if (ready.length > 0) {
276
+ ready
277
+ .sort((a, b) => String(a.priority).localeCompare(String(b.priority)))
278
+ .forEach(t => lines.push(formatKanbanCard(t)));
279
+ }
280
+ lines.push('');
281
+ lines.push('');
282
+ columns.push(false);
283
+
284
+ // ── Done column ──
285
+ lines.push('## Done');
286
+ lines.push('');
287
+ if (done.length > 0) {
288
+ done
289
+ .sort((a, b) => (b.completed_at || '').localeCompare(a.completed_at || ''))
290
+ .forEach(t => lines.push(formatKanbanCard(t)));
291
+ }
292
+ lines.push('');
293
+ lines.push('');
294
+ columns.push(true); // Done column collapsed by default
295
+
296
+ // ── Kanban settings block ──
297
+ lines.push('');
298
+ lines.push('');
299
+ lines.push('%% kanban:settings');
300
+ lines.push('```');
301
+ const settings = existingData.settingsJson || {
302
+ 'kanban-plugin': 'board',
303
+ 'show-checkboxes': true,
304
+ 'move-dates': true,
305
+ };
306
+
307
+ // Always sync list-collapse with the current number of dynamically generated columns
308
+ settings['list-collapse'] = columns;
309
+
310
+ lines.push(JSON.stringify(settings));
311
+ lines.push('```');
312
+ lines.push('%%');
313
+
314
+ return lines.join('\n');
315
+ }
316
+
317
+ // ─── File writer (full Kanban board) ──────────────────────────────────────────
318
+
319
+ /**
320
+ * Write Kanban board content to the Obsidian file.
321
+ * Since Kanban plugin owns the entire file format, we overwrite the full file.
322
+ */
323
+ function writeSyncToFile(filePath, content) {
324
+ const dir = path.dirname(filePath);
325
+ if (!fs.existsSync(dir)) {
326
+ fs.mkdirSync(dir, { recursive: true });
327
+ }
328
+
329
+ const existed = fs.existsSync(filePath);
330
+ fs.writeFileSync(filePath, content + '\n', 'utf8');
331
+ return existed ? 'updated' : 'created';
332
+ }
333
+
334
+ // ─── Sync executor ────────────────────────────────────────────────────────────
335
+
336
+ /**
337
+ * Perform sync for a single project.
338
+ * @param {object} identity - Parsed .project-identity
339
+ * @param {object} obsidianConfig - The automation.obsidian config
340
+ * @param {boolean} dryRun - If true, only print what would happen
341
+ * @returns {boolean} success
342
+ */
343
+ function syncProject(identity, obsidianConfig, dryRun = false) {
344
+ const projectId = identity.projectId;
345
+ const projectName = identity.projectName || projectId;
346
+ const projectIcon = identity.icon || '📁';
347
+ const filePath = obsidianConfig.path;
348
+
349
+ info(`Syncing "${projectName}" → ${filePath}`);
350
+
351
+ // Query project info from Symphony
352
+ const projects = querySqlite(SYMPHONY_DB,
353
+ `SELECT id, name, icon FROM projects WHERE id = '${projectId}'`
354
+ );
355
+
356
+ const projectInfo = projects[0] || {
357
+ id: projectId,
358
+ name: projectName,
359
+ icon: projectIcon,
360
+ };
361
+
362
+ // Query tasks for this project
363
+ const tasks = querySqlite(SYMPHONY_DB,
364
+ `SELECT id, title, status, priority, phase, completed_at FROM tasks WHERE project_id = '${projectId}' ORDER BY status, priority`
365
+ );
366
+
367
+ info(`Found ${tasks.length} tasks for project "${projectId}"`);
368
+
369
+ // Extract existing formatting if any
370
+ const existingData = extractExistingBlocks(filePath);
371
+
372
+ // Render Kanban board
373
+ const content = renderKanbanContent(projectInfo, tasks, existingData);
374
+
375
+ if (dryRun) {
376
+ dim('─── Preview ───');
377
+ log(content);
378
+ dim('─── End Preview ───');
379
+ return true;
380
+ }
381
+
382
+ // Write to file
383
+ const result = writeSyncToFile(filePath, content);
384
+ ok(`Sync complete (${result}): ${filePath}`);
385
+ return true;
386
+ }
387
+
388
+ // ─── CLI handler ──────────────────────────────────────────────────────────────
389
+
390
+ function obsidianHelp() {
391
+ log('');
392
+ log(`${C.cyan}${C.bold}📓 Obsidian Sync${C.reset}`);
393
+ log(`${C.gray} Exports Symphony task data to Obsidian Markdown notes.${C.reset}`);
394
+ log('');
395
+ log(` ${C.green}awkit obsidian sync${C.reset} Sync current project`);
396
+ log(` ${C.green}awkit obsidian status${C.reset} Preview sync (dry-run)`);
397
+ log(` ${C.green}awkit obsidian help${C.reset} Show this help`);
398
+ log('');
399
+ log(`${C.gray} Config: .project-identity → automation.obsidian${C.reset}`);
400
+ log(`${C.gray} Example:${C.reset}`);
401
+ log(`${C.gray} "automation": {${C.reset}`);
402
+ log(`${C.gray} "obsidian": {${C.reset}`);
403
+ log(`${C.gray} "enabled": true,${C.reset}`);
404
+ log(`${C.gray} "path": "/path/to/vault/ProjectNote.md",${C.reset}`);
405
+ log(`${C.gray} "autoSync": true${C.reset}`);
406
+ log(`${C.gray} }${C.reset}`);
407
+ log(`${C.gray} }${C.reset}`);
408
+ log('');
409
+ }
410
+
411
+ /**
412
+ * Main handler for `awkit obsidian` subcommand.
413
+ * @param {string[]} args - CLI args after 'obsidian'
414
+ */
415
+ function cmdObsidian(args) {
416
+ const action = args[0];
417
+
418
+ if (!action || action === 'help' || action === '--help' || action === '-h') {
419
+ obsidianHelp();
420
+ return;
421
+ }
422
+
423
+ // Read project identity
424
+ const identity = readProjectIdentity();
425
+ if (!identity) {
426
+ err('No .project-identity found. Run this from a project directory.');
427
+ return;
428
+ }
429
+
430
+ const obsidianConfig = getObsidianConfig(identity);
431
+ if (!obsidianConfig) {
432
+ err('Obsidian sync not configured. Add automation.obsidian to .project-identity.');
433
+ dim('Example:');
434
+ dim(' "automation": {');
435
+ dim(' "obsidian": {');
436
+ dim(' "enabled": true,');
437
+ dim(' "path": "/path/to/vault/Note.md",');
438
+ dim(' "autoSync": true');
439
+ dim(' }');
440
+ dim(' }');
441
+ return;
442
+ }
443
+
444
+ switch (action) {
445
+ case 'sync':
446
+ log('');
447
+ log(`${C.cyan}${C.bold}📓 Obsidian Sync${C.reset}`);
448
+ log('');
449
+ syncProject(identity, obsidianConfig, false);
450
+ break;
451
+
452
+ case 'status':
453
+ log('');
454
+ log(`${C.cyan}${C.bold}📓 Obsidian Sync — Status (Dry Run)${C.reset}`);
455
+ log('');
456
+ syncProject(identity, obsidianConfig, true);
457
+ break;
458
+
459
+ default:
460
+ err(`Unknown obsidian action: ${action}`);
461
+ obsidianHelp();
462
+ break;
463
+ }
464
+ }
465
+
466
+ // ─── Auto-sync trigger (called from automation-gate) ──────────────────────────
467
+
468
+ /**
469
+ * Silently attempt Obsidian sync if configured.
470
+ * Used as a hook after gate operations (git commit, trello, etc.).
471
+ * Does NOT throw errors — fails silently to avoid blocking gate operations.
472
+ */
473
+ function autoSyncObsidian() {
474
+ try {
475
+ const identity = readProjectIdentity();
476
+ if (!identity) return;
477
+
478
+ const obsidianConfig = getObsidianConfig(identity);
479
+ if (!obsidianConfig || !obsidianConfig.autoSync) return;
480
+
481
+ syncProject(identity, obsidianConfig, false);
482
+ } catch (_) {
483
+ // Silent fail — don't block gate operations
484
+ }
485
+ }
486
+
487
+ // ─── Exports ──────────────────────────────────────────────────────────────────
488
+
489
+ module.exports = {
490
+ cmdObsidian,
491
+ autoSyncObsidian,
492
+ syncProject,
493
+ getObsidianConfig,
494
+ };
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AWK Rename Script — Renames awf → awk throughout main-awf
4
+ * Updates: package.json, VERSION, copies awf.js → awk.js
5
+ * Created by Kien AI
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const AWK_ROOT = path.join(__dirname, '..');
12
+ const C = {
13
+ reset: '\x1b[0m', green: '\x1b[32m', yellow: '\x1b[33m',
14
+ cyan: '\x1b[36m', gray: '\x1b[90m', bold: '\x1b[1m',
15
+ };
16
+ const ok = (m) => console.log(`${C.green}✅ ${m}${C.reset}`);
17
+ const info = (m) => console.log(`${C.cyan}ℹ️ ${m}${C.reset}`);
18
+ const dim = (m) => console.log(`${C.gray} ${m}${C.reset}`);
19
+ const head = (m) => console.log(`\n${C.cyan}${C.bold}── ${m} ──${C.reset}`);
20
+
21
+ async function run() {
22
+ console.log('');
23
+ console.log(`${C.cyan}${C.bold}╔══════════════════════════════════════════════════════════╗${C.reset}`);
24
+ console.log(`${C.cyan}${C.bold}║ 🔤 AWK Rename — awf → awk everywhere ║${C.reset}`);
25
+ console.log(`${C.cyan}${C.bold}╚══════════════════════════════════════════════════════════╝${C.reset}`);
26
+ console.log('');
27
+
28
+ // ── 1. Update package.json ─────────────────────────────────────────────────
29
+ head('package.json');
30
+ const pkgPath = path.join(AWK_ROOT, 'package.json');
31
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
32
+
33
+ pkg.name = '@zeroteam/awk';
34
+ pkg.version = '7.0.0';
35
+ pkg.description = 'AWK v7.0 — Antigravity Workflow Kit. Unified AI agent orchestration system.';
36
+ pkg.bin = { awk: 'bin/awk.js' };
37
+ pkg.scripts = {
38
+ ...pkg.scripts,
39
+ 'install-global': 'node bin/awk.js install',
40
+ 'uninstall-global': 'node bin/awk.js uninstall',
41
+ update: 'node bin/awk.js update',
42
+ test: 'node bin/awk.js doctor',
43
+ harvest: 'node scripts/harvest.js',
44
+ 'harvest-dry': 'node scripts/harvest.js --dry-run',
45
+ };
46
+ pkg.keywords = ['awk', 'antigravity-workflow-kit', 'ai-agent', 'workflow', 'gemini', 'claude'];
47
+
48
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
49
+ ok('package.json updated → @zeroteam/awk v7.0.0');
50
+ dim('bin: { awk: "bin/awk.js" }');
51
+
52
+ // ── 2. Bump VERSION ────────────────────────────────────────────────────────
53
+ head('VERSION');
54
+ const versionPath = path.join(AWK_ROOT, 'VERSION');
55
+ fs.writeFileSync(versionPath, '7.0.0\n');
56
+ ok('VERSION → 7.0.0');
57
+
58
+ // ── 3. Copy awf.js → awk.js (with updated references) ─────────────────────
59
+ head('bin/awk.js');
60
+ const awfJsPath = path.join(AWK_ROOT, 'bin', 'awf.js');
61
+ const awkJsPath = path.join(AWK_ROOT, 'bin', 'awk.js');
62
+
63
+ let content = fs.readFileSync(awfJsPath, 'utf8');
64
+
65
+ // Update banner and references
66
+ content = content
67
+ .replace(/AWF v\$\{AWF_VERSION\}/g, 'AWK v${AWK_VERSION}')
68
+ .replace(/AWF v\$\{AWF_VERSION\}/g, 'AWK v${AWK_VERSION}')
69
+ .replace(/const AWF_VERSION/g, 'const AWK_VERSION')
70
+ .replace(/const AWF_ROOT/g, 'const AWK_ROOT')
71
+ .replace(/AWF_VERSION/g, 'AWK_VERSION')
72
+ .replace(/AWF_ROOT/g, 'AWK_ROOT')
73
+ .replace(/AWF v6\.0 CLI/g, 'AWK v7.0 CLI')
74
+ .replace(/AWF v6\.0 — Antigravity Workflow Framework/g, 'AWK v7.0 — Antigravity Workflow Kit')
75
+ .replace(/Antigravity Workflow Framework/g, 'Antigravity Workflow Kit')
76
+ .replace(/AWF v\$\{/g, 'AWK v${')
77
+ .replace(/AWF installed/g, 'AWK installed')
78
+ .replace(/AWF is healthy/g, 'AWK is healthy')
79
+ .replace(/AWF Health Check/g, 'AWK Health Check')
80
+ .replace(/\bawf\b(?! skills| v| update| version)/g, 'awk')
81
+ // Fix awf_version path reference
82
+ .replace(/awf_version/g, 'awk_version');
83
+
84
+ // Update TARGETS to use awk_version
85
+ content = content.replace(
86
+ "versionFile: path.join(HOME, '.gemini', 'awf_version')",
87
+ "versionFile: path.join(HOME, '.gemini', 'awk_version')"
88
+ );
89
+
90
+ // Update script header comment
91
+ content = content.replace(
92
+ `#!/usr/bin/env node
93
+
94
+ /**
95
+ * AWF v6.0 CLI — Antigravity Workflow Framework`,
96
+ `#!/usr/bin/env node
97
+
98
+ /**
99
+ * AWK v7.0 CLI — Antigravity Workflow Kit`
100
+ );
101
+
102
+ fs.writeFileSync(awkJsPath, content);
103
+ ok('bin/awk.js created');
104
+ dim('All AWF → AWK references updated');
105
+
106
+ // Make executable
107
+ try {
108
+ const { execSync } = require('child_process');
109
+ execSync(`chmod +x "${awkJsPath}"`);
110
+ ok('bin/awk.js is now executable');
111
+ } catch (e) {
112
+ // Ignore
113
+ }
114
+
115
+ // ── 4. Update core/GEMINI.md references ───────────────────────────────────
116
+ head('core/GEMINI.md — Update binary references');
117
+ const geminiPath = path.join(AWK_ROOT, 'core', 'GEMINI.md');
118
+ if (fs.existsSync(geminiPath)) {
119
+ let gemini = fs.readFileSync(geminiPath, 'utf8');
120
+ // Update references from awf install to awk install
121
+ gemini = gemini
122
+ .replace(/\bawf install\b/g, 'awk install')
123
+ .replace(/\bawf doctor\b/g, 'awk doctor')
124
+ .replace(/\bawf update\b/g, 'awk update')
125
+ .replace(/\bnode bin\/awf\.js\b/g, 'node bin/awk.js');
126
+ fs.writeFileSync(geminiPath, gemini);
127
+ ok('core/GEMINI.md binary references updated');
128
+ }
129
+
130
+ // ── 5. Summary ────────────────────────────────────────────────────────────
131
+ console.log('');
132
+ console.log(`${C.gray}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
133
+ console.log(`${C.yellow}${C.bold}🎉 Rename Complete! main-awf is now AWK v7.0${C.reset}`);
134
+ console.log('');
135
+ dim('Package: @zeroteam/awk v7.0.0');
136
+ dim('Binary: bin/awk.js');
137
+ dim('Command: awk install / awk doctor / awk update');
138
+ console.log('');
139
+ console.log(`${C.cyan}Next: node bin/awk.js doctor${C.reset}`);
140
+ console.log('');
141
+ }
142
+
143
+ run().catch(console.error);