archondev 2.16.0 → 2.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +3 -0
  2. package/dist/a11y-O35BAA25.js +14 -0
  3. package/dist/auth-7AUNKGQW.js +14 -0
  4. package/dist/bug-PH2E6GQT.js +13 -0
  5. package/dist/{chunk-P4KDRIFM.js → chunk-23IS6N63.js} +6 -6
  6. package/dist/{chunk-ER4ADSWH.js → chunk-2NSWZDP7.js} +1 -156
  7. package/dist/chunk-3ASILTFB.js +73 -0
  8. package/dist/{chunk-DQE6E436.js → chunk-4KJJ6MSQ.js} +159 -274
  9. package/dist/{chunk-QGM4M3NI.js → chunk-4VNS5WPM.js} +5 -0
  10. package/dist/{chunk-NLW75APJ.js → chunk-4VWKGOBQ.js} +8 -8
  11. package/dist/{chunk-P666JE3G.js → chunk-54ATBLYE.js} +1 -1
  12. package/dist/chunk-5CFGPXQ3.js +160 -0
  13. package/dist/chunk-67ZTMWP4.js +495 -0
  14. package/dist/{chunk-FGH2UX3E.js → chunk-BFPWDOMA.js} +1 -1
  15. package/dist/{chunk-CJ3CFP52.js → chunk-BKJISQXP.js} +76 -24
  16. package/dist/chunk-FWLLGLD5.js +353 -0
  17. package/dist/{chunk-BDPGWWQC.js → chunk-HGLPIM7J.js} +1 -1
  18. package/dist/{chunk-NIKN37AY.js → chunk-HJARQDQR.js} +1 -1
  19. package/dist/{chunk-7NSVJFIZ.js → chunk-HKSVJWMI.js} +1 -1
  20. package/dist/{chunk-35AOCHTE.js → chunk-JAWG5QX4.js} +71 -4
  21. package/dist/chunk-JF7JCK6H.js +485 -0
  22. package/dist/chunk-KY2HKRC2.js +175 -0
  23. package/dist/{chunk-3JURZUY7.js → chunk-OQUWPU5F.js} +6 -4
  24. package/dist/{chunk-KMVMRFQ5.js → chunk-RPVPOUH3.js} +11 -1
  25. package/dist/{chunk-SVU7MLG6.js → chunk-SUGIWSCB.js} +24 -5
  26. package/dist/{chunk-JWY56A3X.js → chunk-U2ZTHVDD.js} +8 -12
  27. package/dist/{chunk-LXXTCZ2Q.js → chunk-UFR2LX6G.js} +216 -14
  28. package/dist/{code-review-ORCNXANW.js → code-review-6MU4UE5M.js} +4 -4
  29. package/dist/{config-BBQW726O.js → config-UARQV6FG.js} +3 -2
  30. package/dist/{constants-AHP5F7HW.js → constants-XDIWFFPN.js} +1 -1
  31. package/dist/execute-6SJL5POT.js +18 -0
  32. package/dist/geo-RP6HKLKZ.js +21 -0
  33. package/dist/index.js +929 -1843
  34. package/dist/{init-6EXMDCWC.js → init-5RS332ZH.js} +2 -2
  35. package/dist/{interviewer-SULVHQW6.js → interviewer-Z7K4IZYG.js} +6 -5
  36. package/dist/{keys-SXJ6MKPY.js → keys-THCHXIFD.js} +1 -1
  37. package/dist/{keys-5Y7KQAHI.js → keys-XE2D6I24.js} +4 -3
  38. package/dist/list-PCDSX4UI.js +17 -0
  39. package/dist/{manager-32P6ZZBP.js → manager-YSNTH2DG.js} +1 -1
  40. package/dist/models-UTFGCHAY.js +33 -0
  41. package/dist/{orchestration-X6LHSHBJ.js → orchestration-HIF3KP25.js} +1 -1
  42. package/dist/{parallel-4NN4ONOH.js → parallel-IRFNVIB6.js} +11 -7
  43. package/dist/{parser-4KJH2PT5.js → parser-BFHETZ5B.js} +1 -1
  44. package/dist/plan-RE5A3E2J.js +22 -0
  45. package/dist/{preferences-32J3GUTY.js → preferences-QAM5QKAQ.js} +6 -6
  46. package/dist/{review-T4ID2QUF.js → review-AUG6GIL6.js} +5 -5
  47. package/dist/seo-PMI42KRZ.js +10 -0
  48. package/dist/{tier-selection-3N4BZYWA.js → tier-selection-2W6JWJUN.js} +4 -3
  49. package/dist/web-checks-4BSYXWDF.js +13 -0
  50. package/package.json +1 -1
  51. package/dist/auth-2E4VORGY.js +0 -14
  52. package/dist/bug-KUMC6HSR.js +0 -11
  53. package/dist/execute-MAFSY5FY.js +0 -16
  54. package/dist/list-C224HUQ6.js +0 -16
  55. package/dist/plan-7DPVPUNK.js +0 -19
@@ -0,0 +1,485 @@
1
+ import {
2
+ recordWebCheckResult
3
+ } from "./chunk-3ASILTFB.js";
4
+
5
+ // src/cli/seo.ts
6
+ import { Command } from "commander";
7
+ import chalk from "chalk";
8
+ import { readFile, writeFile, mkdir } from "fs/promises";
9
+ import { existsSync } from "fs";
10
+ import { join } from "path";
11
+ import { createInterface } from "readline";
12
+ import { glob } from "glob";
13
+ var SEO_CHECKS = {
14
+ title: {
15
+ name: "Title Tag",
16
+ regex: /<title[^>]*>.*?<\/title>/is,
17
+ required: true,
18
+ recommendation: "Add a <title> tag with 50-60 characters describing the page"
19
+ },
20
+ metaDescription: {
21
+ name: "Meta Description",
22
+ regex: /<meta[^>]*name=["']description["'][^>]*>/i,
23
+ required: true,
24
+ recommendation: 'Add <meta name="description" content="..."> with 150-160 characters'
25
+ },
26
+ viewport: {
27
+ name: "Viewport Meta",
28
+ regex: /<meta[^>]*name=["']viewport["'][^>]*>/i,
29
+ required: true,
30
+ recommendation: 'Add <meta name="viewport" content="width=device-width, initial-scale=1">'
31
+ },
32
+ canonical: {
33
+ name: "Canonical URL",
34
+ regex: /<link[^>]*rel=["']canonical["'][^>]*>/i,
35
+ required: false,
36
+ recommendation: 'Add <link rel="canonical" href="https://..."> to specify preferred URL'
37
+ },
38
+ robots: {
39
+ name: "Robots Meta",
40
+ regex: /<meta[^>]*name=["']robots["'][^>]*>/i,
41
+ required: false,
42
+ recommendation: 'Add <meta name="robots" content="index, follow"> for crawl directives'
43
+ },
44
+ ogTitle: {
45
+ name: "Open Graph Title",
46
+ regex: /<meta[^>]*property=["']og:title["'][^>]*>/i,
47
+ required: false,
48
+ recommendation: 'Add <meta property="og:title" content="..."> for social sharing'
49
+ },
50
+ ogDescription: {
51
+ name: "Open Graph Description",
52
+ regex: /<meta[^>]*property=["']og:description["'][^>]*>/i,
53
+ required: false,
54
+ recommendation: 'Add <meta property="og:description" content="..."> for social sharing'
55
+ },
56
+ ogImage: {
57
+ name: "Open Graph Image",
58
+ regex: /<meta[^>]*property=["']og:image["'][^>]*>/i,
59
+ required: false,
60
+ recommendation: 'Add <meta property="og:image" content="https://..."> (1200x630px recommended)'
61
+ },
62
+ ogUrl: {
63
+ name: "Open Graph URL",
64
+ regex: /<meta[^>]*property=["']og:url["'][^>]*>/i,
65
+ required: false,
66
+ recommendation: 'Add <meta property="og:url" content="https://..."> for canonical social URL'
67
+ },
68
+ twitterCard: {
69
+ name: "Twitter Card",
70
+ regex: /<meta[^>]*name=["']twitter:card["'][^>]*>/i,
71
+ required: false,
72
+ recommendation: 'Add <meta name="twitter:card" content="summary_large_image">'
73
+ },
74
+ twitterTitle: {
75
+ name: "Twitter Title",
76
+ regex: /<meta[^>]*name=["']twitter:title["'][^>]*>/i,
77
+ required: false,
78
+ recommendation: 'Add <meta name="twitter:title" content="...">'
79
+ },
80
+ twitterDescription: {
81
+ name: "Twitter Description",
82
+ regex: /<meta[^>]*name=["']twitter:description["'][^>]*>/i,
83
+ required: false,
84
+ recommendation: 'Add <meta name="twitter:description" content="...">'
85
+ },
86
+ twitterImage: {
87
+ name: "Twitter Image",
88
+ regex: /<meta[^>]*name=["']twitter:image["'][^>]*>/i,
89
+ required: false,
90
+ recommendation: 'Add <meta name="twitter:image" content="https://...">'
91
+ }
92
+ };
93
+ function createPrompt() {
94
+ const rl = createInterface({
95
+ input: process.stdin,
96
+ output: process.stdout
97
+ });
98
+ return {
99
+ ask: (question) => new Promise((resolve) => {
100
+ rl.question(question, resolve);
101
+ }),
102
+ close: () => rl.close()
103
+ };
104
+ }
105
+ async function getWebFiles() {
106
+ const patterns = [
107
+ "**/*.html",
108
+ "**/*.htm",
109
+ "**/*.astro",
110
+ "**/*.jsx",
111
+ "**/*.tsx",
112
+ "**/*.vue",
113
+ "**/*.svelte"
114
+ ];
115
+ const ignorePatterns = ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**"];
116
+ let allFiles = [];
117
+ for (const pattern of patterns) {
118
+ const files = await glob(pattern, { cwd: process.cwd(), ignore: ignorePatterns });
119
+ allFiles = allFiles.concat(files.map((f) => join(process.cwd(), f)));
120
+ }
121
+ return allFiles;
122
+ }
123
+ async function scanForSeoIssues(files) {
124
+ const issues = [];
125
+ for (const file of files) {
126
+ try {
127
+ const content = await readFile(file, "utf-8");
128
+ const relativePath = file.replace(process.cwd() + "/", "");
129
+ for (const [checkId, check] of Object.entries(SEO_CHECKS)) {
130
+ const hasTag = check.regex.test(content);
131
+ if (!hasTag) {
132
+ const severity = check.required ? "critical" : checkId.startsWith("og") || checkId.startsWith("twitter") ? "info" : "warning";
133
+ issues.push({
134
+ file: relativePath,
135
+ issue: `Missing ${check.name}`,
136
+ recommendation: check.recommendation,
137
+ severity
138
+ });
139
+ }
140
+ }
141
+ } catch {
142
+ }
143
+ }
144
+ return issues;
145
+ }
146
+ function findHeadInsertionPoint(content) {
147
+ const headMatch = content.match(/<head[^>]*>/i);
148
+ if (headMatch && headMatch.index !== void 0) {
149
+ const afterHead = headMatch.index + headMatch[0].length;
150
+ const nextLineMatch = content.slice(afterHead).match(/\n(\s*)/);
151
+ const indent = nextLineMatch?.[1] ?? " ";
152
+ return { index: afterHead, indent };
153
+ }
154
+ return null;
155
+ }
156
+ async function seoCheck(options) {
157
+ console.log(chalk.blue("\n\u{1F50D} SEO Check\n"));
158
+ console.log(chalk.dim("Scanning for SEO issues...\n"));
159
+ const files = await getWebFiles();
160
+ if (files.length === 0) {
161
+ console.log(chalk.yellow("No web files found to check."));
162
+ return null;
163
+ }
164
+ console.log(chalk.dim(`Scanning ${files.length} files...
165
+ `));
166
+ const issues = await scanForSeoIssues(files);
167
+ const archonDir = join(process.cwd(), ".archon");
168
+ if (!existsSync(archonDir)) {
169
+ await mkdir(archonDir, { recursive: true });
170
+ }
171
+ const report = {
172
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
173
+ totalFiles: files.length,
174
+ issuesFound: issues.length,
175
+ issues,
176
+ passed: issues.length === 0
177
+ };
178
+ await writeFile(join(archonDir, "seo-report.json"), JSON.stringify(report, null, 2));
179
+ await recordWebCheckResult(process.cwd(), "seo", report.passed);
180
+ if (issues.length === 0) {
181
+ console.log(chalk.green("\u2705 SEO Check Passed\n"));
182
+ console.log(chalk.dim("All essential meta tags are present."));
183
+ return report;
184
+ }
185
+ console.log(chalk.yellow(`[!] ${issues.length} SEO Issues Found
186
+ `));
187
+ const criticalCount = issues.filter((i) => i.severity === "critical").length;
188
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
189
+ const infoCount = issues.filter((i) => i.severity === "info").length;
190
+ console.log(chalk.dim("Summary:"));
191
+ if (criticalCount > 0) console.log(chalk.red(` \u2022 ${criticalCount} critical`));
192
+ if (warningCount > 0) console.log(chalk.yellow(` \u2022 ${warningCount} warning`));
193
+ if (infoCount > 0) console.log(chalk.dim(` \u2022 ${infoCount} info`));
194
+ console.log();
195
+ console.log(chalk.bold("File | Issue | Recommendation"));
196
+ console.log(chalk.dim("\u2500".repeat(100)));
197
+ const issuesByFile = /* @__PURE__ */ new Map();
198
+ for (const issue of issues) {
199
+ const existing = issuesByFile.get(issue.file) || [];
200
+ existing.push(issue);
201
+ issuesByFile.set(issue.file, existing);
202
+ }
203
+ for (const [file, fileIssues] of issuesByFile) {
204
+ const displayFile = file.length > 30 ? "..." + file.slice(-27) : file.padEnd(30);
205
+ for (const issue of fileIssues.slice(0, options.verbose ? 20 : 5)) {
206
+ const color = issue.severity === "critical" ? chalk.red : issue.severity === "warning" ? chalk.yellow : chalk.dim;
207
+ const displayIssue = issue.issue.padEnd(24).slice(0, 24);
208
+ const displayRec = issue.recommendation.slice(0, 50);
209
+ console.log(color(`${displayFile} | ${displayIssue} | ${displayRec}`));
210
+ }
211
+ if (!options.verbose && fileIssues.length > 5) {
212
+ console.log(chalk.dim(`${"".padEnd(30)} | ... and ${fileIssues.length - 5} more issues`));
213
+ }
214
+ }
215
+ console.log(chalk.dim(`
216
+ Full report saved to: .archon/seo-report.json`));
217
+ return report;
218
+ }
219
+ async function seoFix(options) {
220
+ const prompt = createPrompt();
221
+ try {
222
+ console.log(chalk.blue("\n\u{1F527} SEO Auto-Fix\n"));
223
+ const reportPath = join(process.cwd(), ".archon/seo-report.json");
224
+ if (!existsSync(reportPath)) {
225
+ console.log(chalk.yellow("No SEO report found. Running check first...\n"));
226
+ await seoCheck({});
227
+ }
228
+ const reportContent = await readFile(reportPath, "utf-8");
229
+ const report = JSON.parse(reportContent);
230
+ if (report.passed || report.issues.length === 0) {
231
+ console.log(chalk.green("No issues to fix!"));
232
+ return;
233
+ }
234
+ const issuesByFile = /* @__PURE__ */ new Map();
235
+ for (const issue of report.issues) {
236
+ const existing = issuesByFile.get(issue.file) || [];
237
+ existing.push(issue);
238
+ issuesByFile.set(issue.file, existing);
239
+ }
240
+ let totalFixes = 0;
241
+ for (const [file, fileIssues] of issuesByFile) {
242
+ const filePath = join(process.cwd(), file);
243
+ let content;
244
+ try {
245
+ content = await readFile(filePath, "utf-8");
246
+ } catch {
247
+ continue;
248
+ }
249
+ const insertPoint = findHeadInsertionPoint(content);
250
+ if (!insertPoint) {
251
+ console.log(chalk.yellow(` ${file}: No <head> tag found, skipping`));
252
+ continue;
253
+ }
254
+ const tagsToAdd = [];
255
+ for (const issue of fileIssues) {
256
+ if (issue.issue === "Missing Title Tag") {
257
+ tagsToAdd.push("<title>Page Title - Site Name</title>");
258
+ } else if (issue.issue === "Missing Meta Description") {
259
+ tagsToAdd.push('<meta name="description" content="Add your page description here (150-160 characters recommended)">');
260
+ } else if (issue.issue === "Missing Viewport Meta") {
261
+ tagsToAdd.push('<meta name="viewport" content="width=device-width, initial-scale=1">');
262
+ } else if (issue.issue === "Missing Canonical URL") {
263
+ tagsToAdd.push('<link rel="canonical" href="https://example.com/page">');
264
+ } else if (issue.issue === "Missing Robots Meta") {
265
+ tagsToAdd.push('<meta name="robots" content="index, follow">');
266
+ }
267
+ }
268
+ if (tagsToAdd.length === 0) continue;
269
+ const newContent = content.slice(0, insertPoint.index) + "\n" + tagsToAdd.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
270
+ console.log(chalk.cyan(`
271
+ ${file}:`));
272
+ for (const tag of tagsToAdd) {
273
+ console.log(chalk.green(` + ${tag.slice(0, 70)}${tag.length > 70 ? "..." : ""}`));
274
+ }
275
+ if (!options.dryRun) {
276
+ const confirm = await prompt.ask(chalk.dim(" Apply these changes? (y/N): "));
277
+ if (confirm.toLowerCase() === "y") {
278
+ await writeFile(filePath, newContent);
279
+ totalFixes += tagsToAdd.length;
280
+ console.log(chalk.green(" \u2713 Applied"));
281
+ } else {
282
+ console.log(chalk.dim(" Skipped"));
283
+ }
284
+ } else {
285
+ totalFixes += tagsToAdd.length;
286
+ }
287
+ }
288
+ if (totalFixes === 0) {
289
+ console.log(chalk.dim("\nNo auto-fixable issues found. Some issues require manual configuration."));
290
+ } else if (options.dryRun) {
291
+ console.log(chalk.yellow(`
292
+ ${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
293
+ } else {
294
+ console.log(chalk.green(`
295
+ \u2705 Applied ${totalFixes} fixes.`));
296
+ console.log(chalk.dim('Run "archon seo check" to verify fixes.'));
297
+ }
298
+ } finally {
299
+ prompt.close();
300
+ }
301
+ }
302
+ async function seoOpenGraph(options) {
303
+ const prompt = createPrompt();
304
+ try {
305
+ console.log(chalk.blue("\n\u{1F4F1} Add Open Graph Tags\n"));
306
+ let targetFile;
307
+ if (options.file) {
308
+ targetFile = options.file.startsWith("/") ? options.file : join(process.cwd(), options.file);
309
+ } else {
310
+ const files = await getWebFiles();
311
+ if (files.length === 0) {
312
+ console.log(chalk.yellow("No web files found."));
313
+ return;
314
+ }
315
+ console.log(chalk.dim("Available files:"));
316
+ files.slice(0, 10).forEach((f, i) => {
317
+ console.log(` ${i + 1}) ${f.replace(process.cwd() + "/", "")}`);
318
+ });
319
+ if (files.length > 10) {
320
+ console.log(chalk.dim(` ... and ${files.length - 10} more`));
321
+ }
322
+ const fileChoice = await prompt.ask("\nEnter file path or number: ");
323
+ const num = parseInt(fileChoice, 10);
324
+ if (num > 0 && num <= files.length) {
325
+ targetFile = files[num - 1] ?? "";
326
+ } else {
327
+ targetFile = fileChoice.startsWith("/") ? fileChoice : join(process.cwd(), fileChoice);
328
+ }
329
+ }
330
+ if (!existsSync(targetFile)) {
331
+ console.log(chalk.red(`File not found: ${targetFile}`));
332
+ return;
333
+ }
334
+ const ogTitle = await prompt.ask("og:title (page title for social): ");
335
+ const ogDescription = await prompt.ask("og:description (page description): ");
336
+ const ogImage = await prompt.ask("og:image (full URL to image): ");
337
+ const ogUrl = await prompt.ask("og:url (canonical page URL): ");
338
+ const tags = [
339
+ `<meta property="og:type" content="website">`,
340
+ `<meta property="og:title" content="${ogTitle}">`,
341
+ `<meta property="og:description" content="${ogDescription}">`,
342
+ `<meta property="og:image" content="${ogImage}">`,
343
+ `<meta property="og:url" content="${ogUrl}">`
344
+ ];
345
+ const content = await readFile(targetFile, "utf-8");
346
+ const insertPoint = findHeadInsertionPoint(content);
347
+ if (!insertPoint) {
348
+ console.log(chalk.yellow("No <head> tag found. Add these tags manually:"));
349
+ tags.forEach((tag) => console.log(chalk.cyan(` ${tag}`)));
350
+ return;
351
+ }
352
+ console.log(chalk.dim("\nTags to add:"));
353
+ tags.forEach((tag) => console.log(chalk.green(` + ${tag}`)));
354
+ const confirm = await prompt.ask("\nApply changes? (y/N): ");
355
+ if (confirm.toLowerCase() === "y") {
356
+ const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
357
+ await writeFile(targetFile, newContent);
358
+ console.log(chalk.green("\n\u2705 Open Graph tags added."));
359
+ } else {
360
+ console.log(chalk.dim("Cancelled."));
361
+ }
362
+ } finally {
363
+ prompt.close();
364
+ }
365
+ }
366
+ async function seoTwitter(options) {
367
+ const prompt = createPrompt();
368
+ try {
369
+ console.log(chalk.blue("\n\u{1F426} Add Twitter Card Tags\n"));
370
+ let targetFile;
371
+ if (options.file) {
372
+ targetFile = options.file.startsWith("/") ? options.file : join(process.cwd(), options.file);
373
+ } else {
374
+ const files = await getWebFiles();
375
+ if (files.length === 0) {
376
+ console.log(chalk.yellow("No web files found."));
377
+ return;
378
+ }
379
+ console.log(chalk.dim("Available files:"));
380
+ files.slice(0, 10).forEach((f, i) => {
381
+ console.log(` ${i + 1}) ${f.replace(process.cwd() + "/", "")}`);
382
+ });
383
+ if (files.length > 10) {
384
+ console.log(chalk.dim(` ... and ${files.length - 10} more`));
385
+ }
386
+ const fileChoice = await prompt.ask("\nEnter file path or number: ");
387
+ const num = parseInt(fileChoice, 10);
388
+ if (num > 0 && num <= files.length) {
389
+ targetFile = files[num - 1] ?? "";
390
+ } else {
391
+ targetFile = fileChoice.startsWith("/") ? fileChoice : join(process.cwd(), fileChoice);
392
+ }
393
+ }
394
+ if (!existsSync(targetFile)) {
395
+ console.log(chalk.red(`File not found: ${targetFile}`));
396
+ return;
397
+ }
398
+ console.log(chalk.dim("Card types: summary, summary_large_image, app, player"));
399
+ const cardType = await prompt.ask("twitter:card type (default: summary_large_image): ") || "summary_large_image";
400
+ const twitterTitle = await prompt.ask("twitter:title: ");
401
+ const twitterDescription = await prompt.ask("twitter:description: ");
402
+ const twitterImage = await prompt.ask("twitter:image (full URL): ");
403
+ const tags = [
404
+ `<meta name="twitter:card" content="${cardType}">`,
405
+ `<meta name="twitter:title" content="${twitterTitle}">`,
406
+ `<meta name="twitter:description" content="${twitterDescription}">`,
407
+ `<meta name="twitter:image" content="${twitterImage}">`
408
+ ];
409
+ const content = await readFile(targetFile, "utf-8");
410
+ const insertPoint = findHeadInsertionPoint(content);
411
+ if (!insertPoint) {
412
+ console.log(chalk.yellow("No <head> tag found. Add these tags manually:"));
413
+ tags.forEach((tag) => console.log(chalk.cyan(` ${tag}`)));
414
+ return;
415
+ }
416
+ console.log(chalk.dim("\nTags to add:"));
417
+ tags.forEach((tag) => console.log(chalk.green(` + ${tag}`)));
418
+ const confirm = await prompt.ask("\nApply changes? (y/N): ");
419
+ if (confirm.toLowerCase() === "y") {
420
+ const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
421
+ await writeFile(targetFile, newContent);
422
+ console.log(chalk.green("\n\u2705 Twitter Card tags added."));
423
+ } else {
424
+ console.log(chalk.dim("Cancelled."));
425
+ }
426
+ } finally {
427
+ prompt.close();
428
+ }
429
+ }
430
+ function createSeoCommand() {
431
+ const seo = new Command("seo").description("SEO checking and meta tag management").addHelpText(
432
+ "after",
433
+ `
434
+ Examples:
435
+ archon seo check Scan files for missing SEO meta tags
436
+ archon seo fix Auto-fix missing essential meta tags
437
+ archon seo fix --dry-run Preview fixes without applying
438
+ archon seo og Add Open Graph tags interactively
439
+ archon seo og --file index.html
440
+ archon seo twitter Add Twitter Card tags interactively
441
+ `
442
+ );
443
+ seo.command("check").description("Scan files for missing SEO meta tags").option("-v, --verbose", "Show all issues (not truncated)").action(async (options) => {
444
+ try {
445
+ await seoCheck(options);
446
+ process.exit(0);
447
+ } catch (error) {
448
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
449
+ process.exit(1);
450
+ }
451
+ });
452
+ seo.command("fix").description("Auto-fix missing essential meta tags").option("--dry-run", "Preview changes without applying").action(async (options) => {
453
+ try {
454
+ await seoFix(options);
455
+ process.exit(0);
456
+ } catch (error) {
457
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
458
+ process.exit(1);
459
+ }
460
+ });
461
+ seo.command("og").description("Add Open Graph meta tags to a file").option("-f, --file <path>", "Target file path").action(async (options) => {
462
+ try {
463
+ await seoOpenGraph(options);
464
+ process.exit(0);
465
+ } catch (error) {
466
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
467
+ process.exit(1);
468
+ }
469
+ });
470
+ seo.command("twitter").description("Add Twitter Card meta tags to a file").option("-f, --file <path>", "Target file path").action(async (options) => {
471
+ try {
472
+ await seoTwitter(options);
473
+ process.exit(0);
474
+ } catch (error) {
475
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
476
+ process.exit(1);
477
+ }
478
+ });
479
+ return seo;
480
+ }
481
+
482
+ export {
483
+ seoCheck,
484
+ createSeoCommand
485
+ };
@@ -0,0 +1,175 @@
1
+ import {
2
+ createAuthedSupabaseClient,
3
+ getAuthToken,
4
+ loadConfig
5
+ } from "./chunk-SUGIWSCB.js";
6
+ import {
7
+ SUPABASE_ANON_KEY,
8
+ SUPABASE_URL
9
+ } from "./chunk-M4LGRTLC.js";
10
+
11
+ // src/cli/cloud.ts
12
+ import chalk from "chalk";
13
+ function getClient(accessToken) {
14
+ return createAuthedSupabaseClient(SUPABASE_URL, SUPABASE_ANON_KEY, accessToken);
15
+ }
16
+ async function cloudStatus() {
17
+ const config = await loadConfig();
18
+ const authToken = getAuthToken(config);
19
+ if (!authToken) {
20
+ console.error(chalk.red('Not authenticated. Run "archon login" first.'));
21
+ process.exit(1);
22
+ }
23
+ const client = getClient(authToken);
24
+ const { data, error } = await client.from("cloud_executions").select("*").order("created_at", { ascending: false }).limit(20);
25
+ if (error) {
26
+ console.error(chalk.red("Failed to fetch cloud executions:"), error.message);
27
+ process.exit(1);
28
+ }
29
+ if (!data || data.length === 0) {
30
+ console.log(chalk.dim("No cloud executions found."));
31
+ return;
32
+ }
33
+ console.log(chalk.bold("\nCloud Executions:\n"));
34
+ for (const exec of data) {
35
+ const statusIcon = getStatusIcon(exec.status);
36
+ const duration = exec.compute_minutes ? `${exec.compute_minutes} min` : "-";
37
+ console.log(`${statusIcon} ${chalk.bold(exec.id.slice(0, 8))} - ${exec.atom_id}`);
38
+ console.log(chalk.dim(` Project: ${exec.project_name ?? "unknown"}`));
39
+ console.log(chalk.dim(` Status: ${exec.status} | Duration: ${duration}`));
40
+ if (exec.result_branch) {
41
+ console.log(chalk.dim(` Branch: ${exec.result_branch}`));
42
+ }
43
+ if (exec.result_pr_url) {
44
+ console.log(chalk.dim(` PR: ${exec.result_pr_url}`));
45
+ }
46
+ if (exec.error_message) {
47
+ console.log(chalk.red(` Error: ${exec.error_message}`));
48
+ }
49
+ console.log();
50
+ }
51
+ }
52
+ async function cloudCancel(executionId) {
53
+ const config = await loadConfig();
54
+ const authToken = getAuthToken(config);
55
+ if (!authToken) {
56
+ console.error(chalk.red('Not authenticated. Run "archon login" first.'));
57
+ process.exit(1);
58
+ }
59
+ const client = getClient(authToken);
60
+ const { data: existing, error: fetchError } = await client.from("cloud_executions").select("id, status").eq("id", executionId).single();
61
+ if (fetchError || !existing) {
62
+ console.error(chalk.red(`Execution ${executionId} not found.`));
63
+ process.exit(1);
64
+ }
65
+ if (existing.status === "completed" || existing.status === "failed" || existing.status === "cancelled") {
66
+ console.log(chalk.yellow(`Execution is already ${existing.status}. Cannot cancel.`));
67
+ return;
68
+ }
69
+ const { error } = await client.from("cloud_executions").update({ status: "cancelled" }).eq("id", executionId);
70
+ if (error) {
71
+ console.error(chalk.red("Failed to cancel execution:"), error.message);
72
+ process.exit(1);
73
+ }
74
+ console.log(chalk.green(`\u2713 Execution ${executionId.slice(0, 8)} cancelled.`));
75
+ }
76
+ async function cloudLogs(executionId) {
77
+ const config = await loadConfig();
78
+ const authToken = getAuthToken(config);
79
+ if (!authToken) {
80
+ console.error(chalk.red('Not authenticated. Run "archon login" first.'));
81
+ process.exit(1);
82
+ }
83
+ const client = getClient(authToken);
84
+ const { data, error } = await client.from("cloud_executions").select("id, atom_id, status, logs").eq("id", executionId).single();
85
+ if (error || !data) {
86
+ console.error(chalk.red(`Execution ${executionId} not found.`));
87
+ process.exit(1);
88
+ }
89
+ const exec = data;
90
+ console.log(chalk.bold(`
91
+ Logs for ${exec.id.slice(0, 8)} (${exec.atom_id}):
92
+ `));
93
+ console.log(chalk.dim(`Status: ${exec.status}
94
+ `));
95
+ const logs = exec.logs;
96
+ if (!logs || logs.length === 0) {
97
+ console.log(chalk.dim("No logs available yet."));
98
+ return;
99
+ }
100
+ for (const log of logs) {
101
+ const ts = log.timestamp ? chalk.dim(`[${log.timestamp}]`) : "";
102
+ const level = log.level === "error" ? chalk.red("ERROR") : log.level === "warn" ? chalk.yellow("WARN") : chalk.dim("INFO");
103
+ console.log(`${ts} ${level} ${log.message}`);
104
+ }
105
+ }
106
+ async function queueCloudExecution(atomId, projectName, options) {
107
+ const config = await loadConfig();
108
+ const authToken = getAuthToken(config);
109
+ if (!authToken) {
110
+ throw new Error('Not authenticated. Run "archon login" first.');
111
+ }
112
+ const client = getClient(authToken);
113
+ const insertData = {
114
+ atom_id: atomId,
115
+ project_name: projectName,
116
+ status: "queued"
117
+ };
118
+ if (options?.repoUrl) {
119
+ insertData["repo_url"] = options.repoUrl;
120
+ }
121
+ if (options?.repoBranch) {
122
+ insertData["repo_branch"] = options.repoBranch;
123
+ }
124
+ if (options?.atomData) {
125
+ insertData["atom_data"] = options.atomData;
126
+ }
127
+ const { data, error } = await client.from("cloud_executions").insert(insertData).select("id").single();
128
+ if (error) {
129
+ throw new Error(`Failed to queue execution: ${error.message}`);
130
+ }
131
+ return data.id;
132
+ }
133
+ async function getGitRemoteUrl(cwd = process.cwd()) {
134
+ try {
135
+ const { execSync } = await import("child_process");
136
+ const output = execSync("git remote get-url origin", { cwd, encoding: "utf-8" });
137
+ return output.trim();
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+ async function getGitBranch(cwd = process.cwd()) {
143
+ try {
144
+ const { execSync } = await import("child_process");
145
+ const output = execSync("git branch --show-current", { cwd, encoding: "utf-8" });
146
+ return output.trim() || "main";
147
+ } catch {
148
+ return "main";
149
+ }
150
+ }
151
+ function getStatusIcon(status) {
152
+ switch (status) {
153
+ case "queued":
154
+ return chalk.blue("\u23F3");
155
+ case "running":
156
+ return chalk.yellow("\u{1F504}");
157
+ case "completed":
158
+ return chalk.green("\u2713");
159
+ case "failed":
160
+ return chalk.red("\u2717");
161
+ case "cancelled":
162
+ return chalk.gray("\u2298");
163
+ default:
164
+ return chalk.dim("?");
165
+ }
166
+ }
167
+
168
+ export {
169
+ cloudStatus,
170
+ cloudCancel,
171
+ cloudLogs,
172
+ queueCloudExecution,
173
+ getGitRemoteUrl,
174
+ getGitBranch
175
+ };
@@ -1,10 +1,12 @@
1
1
  import {
2
- ArchitectAgent,
3
2
  createAtom
4
- } from "./chunk-ER4ADSWH.js";
3
+ } from "./chunk-5CFGPXQ3.js";
4
+ import {
5
+ ArchitectAgent
6
+ } from "./chunk-2NSWZDP7.js";
5
7
  import {
6
8
  loadConfig
7
- } from "./chunk-SVU7MLG6.js";
9
+ } from "./chunk-SUGIWSCB.js";
8
10
 
9
11
  // src/cli/bug.ts
10
12
  import chalk from "chalk";
@@ -399,7 +401,7 @@ async function loadArchitectureForTriage(projectPath) {
399
401
  return null;
400
402
  }
401
403
  try {
402
- const { ArchitectureParser } = await import("./parser-4KJH2PT5.js");
404
+ const { ArchitectureParser } = await import("./parser-BFHETZ5B.js");
403
405
  const parser = new ArchitectureParser(archPath);
404
406
  const result = await parser.parse();
405
407
  return result.success ? result.schema ?? null : null;