@webmaster-droid/cli 0.1.0 → 0.3.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 (2) hide show
  1. package/dist/index.js +259 -122
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
9
9
  import { Command } from "commander";
10
10
  import { glob } from "glob";
11
11
  import { parse as parse2 } from "@babel/parser";
12
- import traverse2 from "@babel/traverse";
12
+ import traverseModule from "@babel/traverse";
13
13
  import * as t2 from "@babel/types";
14
14
  import { createTwoFilesPatch } from "diff";
15
15
  import { createJiti } from "jiti";
@@ -106,6 +106,19 @@ function transformEditableTextCodemod(source, filePath, cwd) {
106
106
 
107
107
  // src/index.ts
108
108
  var program = new Command();
109
+ var CLI_VERSION = "0.1.0-alpha.0";
110
+ var traverse2 = traverseModule.default ?? traverseModule;
111
+ function emitCliEnvelope(payload, isError = false) {
112
+ const out = JSON.stringify(payload, null, 2);
113
+ if (isError) {
114
+ console.error(out);
115
+ return;
116
+ }
117
+ console.log(out);
118
+ }
119
+ function errorToMessage(error) {
120
+ return error instanceof Error ? error.message : String(error);
121
+ }
109
122
  async function ensureDir(filePath) {
110
123
  await fs.mkdir(path2.dirname(filePath), { recursive: true });
111
124
  }
@@ -113,44 +126,55 @@ async function readJson(filePath) {
113
126
  const raw = await fs.readFile(filePath, "utf8");
114
127
  return JSON.parse(raw);
115
128
  }
116
- program.name("webmaster-droid").description("Webmaster Droid CLI").version("0.1.0-alpha.0");
117
- program.command("init").description("Initialize optional webmaster-droid config in current project").option("--framework <framework>", "framework", "next").option("--backend <backend>", "backend", "aws").option("--out <dir>", "output dir", ".").action(async (opts) => {
118
- const outDir = path2.resolve(process.cwd(), opts.out);
119
- const configPath = path2.join(outDir, "webmaster-droid.config.ts");
120
- await ensureDir(configPath);
121
- const config = `export default {
122
- framework: "${opts.framework}",
123
- backend: "${opts.backend}",
124
- apiBaseUrlEnv: "NEXT_PUBLIC_AGENT_API_BASE_URL"
125
- };
126
- `;
127
- try {
128
- await fs.access(configPath);
129
- console.log(`Config already exists: ${configPath}`);
130
- } catch {
131
- await fs.writeFile(configPath, config, "utf8");
132
- console.log(`Created: ${configPath}`);
129
+ program.name("webmaster-droid").description("Webmaster Droid CLI").version(CLI_VERSION);
130
+ program.command("init").description("Initialize webmaster-droid environment template in current project").option("--backend <backend>", "backend (supabase|aws)", "supabase").option("--out <dir>", "output dir", ".").action(async (opts) => {
131
+ const backendRaw = String(opts.backend ?? "supabase").trim().toLowerCase();
132
+ if (backendRaw !== "supabase" && backendRaw !== "aws") {
133
+ throw new Error(`Unsupported backend '${opts.backend}'. Expected 'supabase' or 'aws'.`);
133
134
  }
135
+ const backend = backendRaw;
136
+ const outDir = path2.resolve(process.cwd(), opts.out);
134
137
  const envExample = path2.join(outDir, ".env.webmaster-droid.example");
138
+ let createdEnvTemplate = false;
135
139
  try {
136
140
  await fs.access(envExample);
137
141
  } catch {
142
+ await ensureDir(envExample);
138
143
  await fs.writeFile(
139
144
  envExample,
140
145
  [
141
146
  "NEXT_PUBLIC_AGENT_API_BASE_URL=http://localhost:8787",
142
- "CMS_S3_BUCKET=",
143
- "CMS_S3_REGION=",
144
- "CMS_PUBLIC_BASE_URL=https://your-domain.example",
147
+ "",
148
+ "# Supabase (default backend)",
149
+ "NEXT_PUBLIC_SUPABASE_URL=",
150
+ "NEXT_PUBLIC_SUPABASE_ANON_KEY=",
151
+ "SUPABASE_URL=",
152
+ "SUPABASE_ANON_KEY=",
153
+ "SUPABASE_SERVICE_ROLE_KEY=",
145
154
  "SUPABASE_JWKS_URL=",
155
+ "CMS_SUPABASE_BUCKET=webmaster-droid-cms",
156
+ "CMS_STORAGE_PREFIX=cms",
157
+ "",
158
+ "# Shared runtime",
159
+ "CMS_PUBLIC_BASE_URL=https://your-domain.example",
146
160
  "MODEL_OPENAI_ENABLED=true",
147
161
  "MODEL_GEMINI_ENABLED=true",
148
- "DEFAULT_MODEL_ID=openai:gpt-5.2"
162
+ "DEFAULT_MODEL_ID=openai:gpt-5.2",
163
+ "",
164
+ "# AWS (optional backend)",
165
+ "CMS_S3_BUCKET=",
166
+ "CMS_S3_REGION="
149
167
  ].join("\n") + "\n",
150
168
  "utf8"
151
169
  );
170
+ createdEnvTemplate = true;
171
+ }
172
+ if (createdEnvTemplate) {
152
173
  console.log(`Created: ${envExample}`);
174
+ } else {
175
+ console.log(`Env template already exists: ${envExample}`);
153
176
  }
177
+ console.log(`Backend preset: ${backend}`);
154
178
  });
155
179
  var schema = program.command("schema").description("Optional schema helpers");
156
180
  schema.command("init").description("Create starter schema file").option("--out <file>", "schema output", "cms/schema.webmaster.ts").action(async (opts) => {
@@ -193,117 +217,169 @@ schema.command("build").description("Compile schema file to runtime manifest JSO
193
217
  await fs.writeFile(output, JSON.stringify(manifest, null, 2) + "\n", "utf8");
194
218
  console.log(`Wrote manifest: ${output}`);
195
219
  });
196
- program.command("scan").description("Scan source files for static content candidates").argument("<srcDir>", "source directory").option("--out <file>", "report output", ".webmaster-droid/scan-report.json").action(async (srcDir, opts) => {
197
- const root = path2.resolve(process.cwd(), srcDir);
198
- const files = await glob("**/*.{ts,tsx,js,jsx}", {
199
- cwd: root,
200
- absolute: true,
201
- ignore: ["**/*.d.ts", "**/node_modules/**", "**/.next/**", "**/dist/**"]
202
- });
203
- const findings = [];
204
- for (const file of files) {
205
- const code = await fs.readFile(file, "utf8");
206
- const ast = parse2(code, {
207
- sourceType: "module",
208
- plugins: ["typescript", "jsx"]
220
+ program.command("scan").description("Scan source files for static content candidates").argument("<srcDir>", "source directory").option("--out <file>", "report output", ".webmaster-droid/scan-report.json").option("--json", "emit machine-readable JSON output", false).action(async (srcDir, opts) => {
221
+ try {
222
+ const root = path2.resolve(process.cwd(), srcDir);
223
+ const files = await glob("**/*.{ts,tsx,js,jsx}", {
224
+ cwd: root,
225
+ absolute: true,
226
+ ignore: ["**/*.d.ts", "**/node_modules/**", "**/.next/**", "**/dist/**"]
209
227
  });
210
- traverse2(ast, {
211
- JSXText(pathNode) {
212
- const text = normalizeText(pathNode.node.value);
213
- if (!text || text.length < 3) {
214
- return;
228
+ const findings = [];
229
+ for (const file of files) {
230
+ const code = await fs.readFile(file, "utf8");
231
+ const ast = parse2(code, {
232
+ sourceType: "module",
233
+ plugins: ["typescript", "jsx"]
234
+ });
235
+ traverse2(ast, {
236
+ JSXText(pathNode) {
237
+ const text = normalizeText(pathNode.node.value);
238
+ if (!text || text.length < 3) {
239
+ return;
240
+ }
241
+ findings.push({
242
+ type: "jsx-text",
243
+ file: path2.relative(process.cwd(), file),
244
+ line: pathNode.node.loc?.start.line,
245
+ column: pathNode.node.loc?.start.column,
246
+ text
247
+ });
248
+ },
249
+ JSXAttribute(pathNode) {
250
+ const name = t2.isJSXIdentifier(pathNode.node.name) ? pathNode.node.name.name : "";
251
+ if (!["src", "href", "alt", "title"].includes(name)) {
252
+ return;
253
+ }
254
+ const valueNode = pathNode.node.value;
255
+ if (!valueNode || !t2.isStringLiteral(valueNode)) {
256
+ return;
257
+ }
258
+ findings.push({
259
+ type: "jsx-attr",
260
+ attr: name,
261
+ file: path2.relative(process.cwd(), file),
262
+ line: valueNode.loc?.start.line,
263
+ column: valueNode.loc?.start.column,
264
+ text: valueNode.value
265
+ });
215
266
  }
216
- findings.push({
217
- type: "jsx-text",
218
- file: path2.relative(process.cwd(), file),
219
- line: pathNode.node.loc?.start.line,
220
- column: pathNode.node.loc?.start.column,
221
- text
222
- });
223
- },
224
- JSXAttribute(pathNode) {
225
- const name = t2.isJSXIdentifier(pathNode.node.name) ? pathNode.node.name.name : "";
226
- if (!["src", "href", "alt", "title"].includes(name)) {
227
- return;
228
- }
229
- const valueNode = pathNode.node.value;
230
- if (!valueNode || !t2.isStringLiteral(valueNode)) {
231
- return;
267
+ });
268
+ }
269
+ const output = path2.resolve(process.cwd(), opts.out);
270
+ const report = {
271
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
272
+ source: root,
273
+ totalFiles: files.length,
274
+ totalFindings: findings.length,
275
+ findings
276
+ };
277
+ await ensureDir(output);
278
+ await fs.writeFile(output, JSON.stringify(report, null, 2) + "\n", "utf8");
279
+ if (opts.json) {
280
+ emitCliEnvelope({
281
+ ok: true,
282
+ command: "scan",
283
+ version: CLI_VERSION,
284
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
285
+ data: {
286
+ reportPath: output,
287
+ source: root,
288
+ totalFiles: files.length,
289
+ totalFindings: findings.length
232
290
  }
233
- findings.push({
234
- type: "jsx-attr",
235
- attr: name,
236
- file: path2.relative(process.cwd(), file),
237
- line: valueNode.loc?.start.line,
238
- column: valueNode.loc?.start.column,
239
- text: valueNode.value
240
- });
241
- }
242
- });
243
- }
244
- const output = path2.resolve(process.cwd(), opts.out);
245
- await ensureDir(output);
246
- await fs.writeFile(
247
- output,
248
- JSON.stringify(
291
+ });
292
+ return;
293
+ }
294
+ console.log(`Scan complete. Findings: ${findings.length}. Report: ${output}`);
295
+ } catch (error) {
296
+ if (!opts.json) {
297
+ throw error;
298
+ }
299
+ emitCliEnvelope(
249
300
  {
250
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
251
- source: root,
252
- totalFiles: files.length,
253
- totalFindings: findings.length,
254
- findings
301
+ ok: false,
302
+ command: "scan",
303
+ version: CLI_VERSION,
304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
305
+ errors: [errorToMessage(error)]
255
306
  },
256
- null,
257
- 2
258
- ) + "\n",
259
- "utf8"
260
- );
261
- console.log(`Scan complete. Findings: ${findings.length}. Report: ${output}`);
307
+ true
308
+ );
309
+ process.exitCode = 1;
310
+ }
262
311
  });
263
- program.command("codemod").description("Apply deterministic JSX codemods to Editable components").argument("<srcDir>", "source directory").option("--apply", "write file changes", false).option("--out <file>", "report output", ".webmaster-droid/codemod-report.json").action(async (srcDir, opts) => {
264
- const root = path2.resolve(process.cwd(), srcDir);
265
- const files = await glob("**/*.{tsx,jsx}", {
266
- cwd: root,
267
- absolute: true,
268
- ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"]
269
- });
270
- const changed = [];
271
- for (const file of files) {
272
- const source = await fs.readFile(file, "utf8");
273
- const transformed = transformEditableTextCodemod(source, file, process.cwd());
274
- if (!transformed.changed) {
275
- continue;
276
- }
277
- const next = transformed.next;
278
- const relFile = path2.relative(process.cwd(), file);
279
- changed.push({
280
- file: relFile,
281
- patch: createTwoFilesPatch(relFile, relFile, source, next)
312
+ program.command("codemod").description("Apply deterministic JSX codemods to Editable components").argument("<srcDir>", "source directory").option("--apply", "write file changes", false).option("--out <file>", "report output", ".webmaster-droid/codemod-report.json").option("--json", "emit machine-readable JSON output", false).action(async (srcDir, opts) => {
313
+ try {
314
+ const root = path2.resolve(process.cwd(), srcDir);
315
+ const files = await glob("**/*.{tsx,jsx}", {
316
+ cwd: root,
317
+ absolute: true,
318
+ ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"]
282
319
  });
283
- if (opts.apply) {
284
- await fs.writeFile(file, next, "utf8");
320
+ const changed = [];
321
+ for (const file of files) {
322
+ const source = await fs.readFile(file, "utf8");
323
+ const transformed = transformEditableTextCodemod(source, file, process.cwd());
324
+ if (!transformed.changed) {
325
+ continue;
326
+ }
327
+ const next = transformed.next;
328
+ const relFile = path2.relative(process.cwd(), file);
329
+ changed.push({
330
+ file: relFile,
331
+ patch: createTwoFilesPatch(relFile, relFile, source, next)
332
+ });
333
+ if (opts.apply) {
334
+ await fs.writeFile(file, next, "utf8");
335
+ }
285
336
  }
286
- }
287
- const output = path2.resolve(process.cwd(), opts.out);
288
- await ensureDir(output);
289
- await fs.writeFile(
290
- output,
291
- JSON.stringify(
337
+ const output = path2.resolve(process.cwd(), opts.out);
338
+ const report = {
339
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
340
+ source: root,
341
+ apply: Boolean(opts.apply),
342
+ changedFiles: changed.length,
343
+ changes: changed
344
+ };
345
+ await ensureDir(output);
346
+ await fs.writeFile(output, JSON.stringify(report, null, 2) + "\n", "utf8");
347
+ if (opts.json) {
348
+ emitCliEnvelope({
349
+ ok: true,
350
+ command: "codemod",
351
+ version: CLI_VERSION,
352
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
353
+ data: {
354
+ reportPath: output,
355
+ source: root,
356
+ apply: Boolean(opts.apply),
357
+ changedFiles: changed.length
358
+ }
359
+ });
360
+ return;
361
+ }
362
+ console.log(
363
+ `${opts.apply ? "Applied" : "Previewed"} codemod changes: ${changed.length}. Report: ${output}`
364
+ );
365
+ } catch (error) {
366
+ if (!opts.json) {
367
+ throw error;
368
+ }
369
+ emitCliEnvelope(
292
370
  {
293
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
294
- source: root,
295
- apply: Boolean(opts.apply),
296
- changedFiles: changed.length,
297
- changes: changed
371
+ ok: false,
372
+ command: "codemod",
373
+ version: CLI_VERSION,
374
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
375
+ errors: [errorToMessage(error)]
298
376
  },
299
- null,
300
- 2
301
- ) + "\n",
302
- "utf8"
303
- );
304
- console.log(`${opts.apply ? "Applied" : "Previewed"} codemod changes: ${changed.length}. Report: ${output}`);
377
+ true
378
+ );
379
+ process.exitCode = 1;
380
+ }
305
381
  });
306
- program.command("doctor").description("Validate local environment for webmaster-droid").action(async () => {
382
+ program.command("doctor").description("Validate local environment for webmaster-droid").option("--json", "emit machine-readable JSON output", false).action(async (opts) => {
307
383
  const issues = [];
308
384
  const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
309
385
  if (!Number.isFinite(major) || major < 20) {
@@ -315,6 +391,23 @@ program.command("doctor").description("Validate local environment for webmaster-
315
391
  issues.push("package.json missing in current working directory");
316
392
  }
317
393
  if (issues.length > 0) {
394
+ if (opts.json) {
395
+ emitCliEnvelope(
396
+ {
397
+ ok: false,
398
+ command: "doctor",
399
+ version: CLI_VERSION,
400
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
401
+ data: {
402
+ checksPassed: false
403
+ },
404
+ errors: issues
405
+ },
406
+ true
407
+ );
408
+ process.exitCode = 1;
409
+ return;
410
+ }
318
411
  console.error("Doctor found issues:");
319
412
  for (const issue of issues) {
320
413
  console.error(`- ${issue}`);
@@ -322,6 +415,18 @@ program.command("doctor").description("Validate local environment for webmaster-
322
415
  process.exitCode = 1;
323
416
  return;
324
417
  }
418
+ if (opts.json) {
419
+ emitCliEnvelope({
420
+ ok: true,
421
+ command: "doctor",
422
+ version: CLI_VERSION,
423
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
424
+ data: {
425
+ checksPassed: true
426
+ }
427
+ });
428
+ return;
429
+ }
325
430
  console.log("Doctor checks passed.");
326
431
  });
327
432
  program.command("dev").description("Start project dev command (pass-through)").option("--cmd <command>", "command to run", "npm run dev").action(async (opts) => {
@@ -361,6 +466,38 @@ aws.requiredOption("--entry <file>", "entry TypeScript file").requiredOption("--
361
466
  await run(`aws lambda wait function-updated --region ${opts.region} --function-name ${fn}`);
362
467
  }
363
468
  });
469
+ var supabase = deploy.command("supabase").description("Deploy Supabase edge functions");
470
+ supabase.requiredOption("--project-ref <ref>", "Supabase project reference").requiredOption("--functions <names>", "comma-separated function names").option("--env-file <path>", "path to env file for function deployment").option("--no-verify-jwt", "disable JWT verification for deployed functions").action(async (opts) => {
471
+ const functions = String(opts.functions).split(",").map((item) => item.trim()).filter(Boolean);
472
+ if (functions.length === 0) {
473
+ throw new Error("No function names provided.");
474
+ }
475
+ const run = (cmd) => new Promise((resolve, reject) => {
476
+ const child = spawn(cmd, { stdio: "inherit", shell: true });
477
+ child.on("exit", (code) => {
478
+ if (code === 0) {
479
+ resolve();
480
+ } else {
481
+ reject(new Error(`Command failed: ${cmd}`));
482
+ }
483
+ });
484
+ });
485
+ for (const fn of functions) {
486
+ const parts = [
487
+ "supabase functions deploy",
488
+ fn,
489
+ "--project-ref",
490
+ opts.projectRef
491
+ ];
492
+ if (opts.envFile) {
493
+ parts.push("--env-file", opts.envFile);
494
+ }
495
+ if (opts.verifyJwt === false) {
496
+ parts.push("--no-verify-jwt");
497
+ }
498
+ await run(parts.join(" "));
499
+ }
500
+ });
364
501
  var skill = program.command("skill").description("Skill helpers");
365
502
  skill.command("install").description("Install bundled conversion skill into CODEX_HOME").option("--codex-home <dir>", "CODEX_HOME path override").option("--force", "overwrite existing", false).action(async (opts) => {
366
503
  const cliDir = path2.dirname(fileURLToPath(import.meta.url));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmaster-droid/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "webmaster-droid": "dist/index.js"