@topy-ai/maggie 0.7.52 → 0.7.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-zh-TW.md +28 -4
- package/README.md +93 -60
- package/bin/maggie.js +67 -12
- package/bundled-contracts/maggie-assistant-tools/booking-receptionist-example-v1.json +161 -0
- package/bundled-contracts/maggie-assistant-tools/tool-registry-v1.schema.json +268 -20
- package/bundled-contracts/maggie-deployment/bridge-host-example-v1.json +49 -7
- package/bundled-contracts/maggiedash/booking-email-templates-v1.json +181 -13
- package/bundled-contracts/maggiedash/booking-rules-v1.json +1699 -0
- package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +398 -39
- package/bundled-references/maggiedash-booking/PRD.md +22 -11
- package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +1 -1
- package/bundled-references/maggiedash-booking/RULES.md +167 -0
- package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +2 -2
- package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +42 -4
- package/bundled-references/provider-adapter-contract.md +16 -3
- package/bundled-skills/catalog.json +1 -1
- package/bundled-skills/maggie-assistant-tools/SKILL.md +54 -3
- package/bundled-skills/maggie-assistant-tools/references/approvals-and-audit.md +27 -0
- package/bundled-skills/maggie-assistant-tools/references/booking-receptionist.md +45 -0
- package/bundled-skills/maggie-assistant-tools/references/bridge-host.md +54 -0
- package/bundled-skills/maggie-auth-reference/SKILL.md +18 -0
- package/bundled-skills/maggie-blog/SKILL.md +8 -0
- package/bundled-skills/maggie-booking/SKILL.md +66 -14
- package/bundled-skills/maggie-content-localization/SKILL.md +26 -0
- package/bundled-skills/maggie-dash/SKILL.md +28 -2
- package/bundled-skills/maggie-deployment/SKILL.md +48 -0
- package/bundled-skills/maggie-deployment/references/cloudflare.md +19 -0
- package/bundled-skills/maggie-deployment/references/vps.md +92 -0
- package/bundled-skills/maggie-design/SKILL.md +98 -3
- package/bundled-skills/maggie-design/references/app-ui-patterns.md +79 -0
- package/bundled-skills/maggie-feedback/SKILL.md +27 -2
- package/bundled-skills/maggie-marketplace/SKILL.md +12 -0
- package/bundled-skills/maggie-marketplace/references/directory-search.md +51 -0
- package/bundled-skills/maggie-marketplace/references/directory.mjs +112 -0
- package/bundled-skills/maggie-mobile-app/SKILL.md +65 -6
- package/bundled-skills/maggie-mobile-app/references/payments-and-push.md +78 -0
- package/bundled-skills/maggie-qa-workflow/SKILL.md +60 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +33 -0
- package/bundled-skills/maggie-service-booking/SKILL.md +28 -3
- package/bundled-skills/maggie-social-share/SKILL.md +56 -4
- package/bundled-skills/maggie-social-share/references/queue-and-connections.md +28 -0
- package/bundled-tools/clis/maggie_assistant_tools.py +84 -3
- package/bundled-tools/clis/maggie_booking.py +24 -12
- package/bundled-tools/clis/maggie_booking_connect_qa.py +136 -0
- package/bundled-tools/clis/maggie_booking_rules.py +476 -0
- package/bundled-tools/clis/maggie_bridge_host.py +77 -1
- package/bundled-tools/clis/maggie_dash.py +173 -3
- package/bundled-tools/clis/maggie_deployment_ops.py +524 -0
- package/bundled-tools/clis/maggie_design.py +15 -2
- package/bundled-tools/clis/maggie_design_bulk.py +290 -0
- package/bundled-tools/clis/maggie_design_lint.py +205 -3
- package/bundled-tools/clis/maggie_feedback.py +138 -28
- package/bundled-tools/clis/maggie_live_urls.py +138 -0
- package/bundled-tools/clis/maggie_localization.py +3 -0
- package/bundled-tools/clis/maggie_localization_keys.py +151 -0
- package/bundled-tools/clis/maggie_media_generate.py +174 -0
- package/bundled-tools/clis/maggie_mobile.py +219 -4
- package/bundled-tools/clis/maggie_product_release.py +49 -1
- package/bundled-tools/clis/maggie_qa_app_preflight.py +122 -4
- package/bundled-tools/clis/maggie_qa_fit.py +217 -0
- package/bundled-tools/clis/maggie_qa_workflow.py +5 -0
- package/bundled-tools/clis/maggie_schema_complexity.py +84 -0
- package/bundled-tools/clis/maggie_seo_routes.py +154 -0
- package/bundled-tools/clis/maggie_service_migration.py +272 -0
- package/bundled-tools/clis/maggie_social_queue.py +2 -0
- package/bundled-tools/clis/maggie_social_share.py +174 -7
- package/bundled-tools/integrations/maggie-api-pull.md +22 -0
- package/bundled-tools/runtime/assistant_approval.py +98 -0
- package/package.json +1 -1
- package/references/provider-adapter-contract.md +16 -3
- package/bundled-contracts/maggiedash/execution-board.json +0 -5463
- package/bundled-references/maggiedash-booking/CURRENT-STATE.md +0 -210
- package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +0 -5463
- package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +0 -270
- package/bundled-references/maggiedash-booking/PROGRESS.md +0 -279
- package/bundled-references/maggiedash-booking/README.md +0 -288
- package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +0 -551
- package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +0 -107
- package/bundled-references/maggiedash-booking/TASKS.md +0 -178
|
@@ -252,7 +252,7 @@ def merge_astro_middleware(path: Path) -> dict[str, object]:
|
|
|
252
252
|
return {"status": "updated", "reason": "inserted idempotent Maggie rewrite block"}
|
|
253
253
|
|
|
254
254
|
|
|
255
|
-
def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Path, framework: str | None) -> list[tuple[Path, Path, Path]]:
|
|
255
|
+
def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Path, framework: str | None, required: bool = True) -> list[tuple[Path, Path, Path]]:
|
|
256
256
|
"""Return non-destructive host files for a declared framework adapter."""
|
|
257
257
|
if not framework:
|
|
258
258
|
return []
|
|
@@ -260,6 +260,8 @@ def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Pa
|
|
|
260
260
|
if not isinstance(bootstrap, dict):
|
|
261
261
|
return []
|
|
262
262
|
spec = bootstrap.get(framework)
|
|
263
|
+
if not isinstance(spec, dict) and not required:
|
|
264
|
+
return []
|
|
263
265
|
if not isinstance(spec, dict):
|
|
264
266
|
raise RuntimeError(
|
|
265
267
|
f"MaggieDash distribution at {source} does not provide a {framework} host bootstrap; "
|
|
@@ -288,6 +290,128 @@ def manifest_host_file_pairs(source: Path, manifest: dict[str, object], root: Pa
|
|
|
288
290
|
return pairs
|
|
289
291
|
|
|
290
292
|
|
|
293
|
+
CORE_MODULE = "core"
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def module_names(manifest: dict[str, object]) -> list[str]:
|
|
297
|
+
modules = manifest.get("modules")
|
|
298
|
+
return sorted(modules) if isinstance(modules, dict) else []
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def effective_manifest(manifest: dict[str, object], modules: list[str]) -> dict[str, object]:
|
|
302
|
+
"""Core entries plus the root files and workspaces of the chosen modules."""
|
|
303
|
+
result = dict(manifest)
|
|
304
|
+
specs = manifest.get("modules") if isinstance(manifest.get("modules"), dict) else {}
|
|
305
|
+
root_files = list(manifest.get("rootFiles", []))
|
|
306
|
+
workspaces = list(manifest.get("workspaces", []))
|
|
307
|
+
for name in modules:
|
|
308
|
+
spec = specs.get(name) or {}
|
|
309
|
+
root_files.extend(spec.get("rootFiles", []))
|
|
310
|
+
workspaces.extend(spec.get("workspaces", []))
|
|
311
|
+
result["rootFiles"] = root_files
|
|
312
|
+
result["workspaces"] = workspaces
|
|
313
|
+
return result
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def resolve_modules(manifest: dict[str, object], requested: list[str], previous: list[str]) -> list[str]:
|
|
317
|
+
available = module_names(manifest)
|
|
318
|
+
if requested and not available:
|
|
319
|
+
# A release without the module split installs everything already.
|
|
320
|
+
return []
|
|
321
|
+
unknown = sorted(set(requested) - set(available))
|
|
322
|
+
if unknown:
|
|
323
|
+
raise RuntimeError(f"unknown MaggieDash module(s): {', '.join(unknown)}; this release offers: {', '.join(available) or 'none'}")
|
|
324
|
+
# Re-running install keeps refreshing modules that were added before.
|
|
325
|
+
return sorted((set(requested) | set(previous)) & set(available))
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def installed_state(root: Path) -> dict[str, object]:
|
|
329
|
+
try:
|
|
330
|
+
value = json.loads((root / ".maggie" / "dash-install.json").read_text(encoding="utf-8"))
|
|
331
|
+
except (OSError, json.JSONDecodeError):
|
|
332
|
+
return {}
|
|
333
|
+
return value if isinstance(value, dict) else {}
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def package_dependencies(root: Path) -> set[str]:
|
|
337
|
+
try:
|
|
338
|
+
value = json.loads((root / "package.json").read_text(encoding="utf-8"))
|
|
339
|
+
except (OSError, json.JSONDecodeError):
|
|
340
|
+
return set()
|
|
341
|
+
return set(value.get("dependencies") or {}) | set(value.get("devDependencies") or {})
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
SERVICE_WORDS = re.compile(r"\b(book (?:now|an? (?:appointment|session|treatment|table))|appointments?|treatments?|reservations?|book online|opening hours)\b", re.I)
|
|
345
|
+
LOCALE_DIR = re.compile(r"^(?:[a-z]{2}(?:-[a-z]{2,4})?)$")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def analyse_host(root: Path, framework: str | None) -> dict[str, object]:
|
|
349
|
+
"""What the host already has, read from files only (nothing is executed)."""
|
|
350
|
+
deps = package_dependencies(root)
|
|
351
|
+
auth = sorted(name for name in deps if name in {"firebase", "firebase-admin", "next-auth", "lucia", "better-auth", "@supabase/supabase-js", "@supabase/ssr", "auth-astro"} or name.startswith(("@clerk/", "@auth/")))
|
|
352
|
+
database = sorted(name for name in deps if name in {"pg", "postgres", "@neondatabase/serverless", "drizzle-orm", "prisma", "@prisma/client", "mysql2", "better-sqlite3", "@libsql/client", "kysely"})
|
|
353
|
+
for config in ("wrangler.json", "wrangler.jsonc", "wrangler.toml"):
|
|
354
|
+
path = root / config
|
|
355
|
+
if path.is_file() and "d1_databases" in path.read_text(encoding="utf-8", errors="replace"):
|
|
356
|
+
database.append("cloudflare-d1")
|
|
357
|
+
payments = sorted(name for name in deps if name in {"stripe", "@stripe/stripe-js", "@paypal/checkout-server-sdk", "@lemonsqueezy/lemonsqueezy.js"})
|
|
358
|
+
source = root / "src"
|
|
359
|
+
service_hits: list[str] = []
|
|
360
|
+
if source.is_dir():
|
|
361
|
+
for path in sorted(source.rglob("*")):
|
|
362
|
+
if len(service_hits) >= 5:
|
|
363
|
+
break
|
|
364
|
+
if path.is_file() and path.suffix in {".astro", ".md", ".mdx", ".tsx", ".jsx", ".ts", ".json", ".html", ".vue", ".svelte"} and "_maggie" not in path.parts and "maggie" not in path.parts:
|
|
365
|
+
try:
|
|
366
|
+
if SERVICE_WORDS.search(path.read_text(encoding="utf-8", errors="replace")[:200_000]):
|
|
367
|
+
service_hits.append(str(path.relative_to(root)))
|
|
368
|
+
except OSError:
|
|
369
|
+
continue
|
|
370
|
+
blog = any((root / candidate).exists() for candidate in ("src/content/blog", "src/content/posts", "src/pages/blog", "src/pages/blog.astro", "src/pages/posts", "content/blog", "app/blog"))
|
|
371
|
+
locales: set[str] = set()
|
|
372
|
+
for pages in (root / "src" / "pages", root / "app"):
|
|
373
|
+
if pages.is_dir():
|
|
374
|
+
locales.update(child.name for child in pages.iterdir() if child.is_dir() and LOCALE_DIR.match(child.name) and child.name not in {"api", "og"})
|
|
375
|
+
astro_config = next((path for path in root.glob("astro.config.*")), None)
|
|
376
|
+
if astro_config:
|
|
377
|
+
match = re.search(r"locales\s*:\s*\[([^\]]+)\]", astro_config.read_text(encoding="utf-8", errors="replace"))
|
|
378
|
+
if match:
|
|
379
|
+
locales.update(re.findall(r"[\"']([A-Za-z-]+)[\"']", match.group(1)))
|
|
380
|
+
return {
|
|
381
|
+
"framework": framework,
|
|
382
|
+
"auth": auth,
|
|
383
|
+
"database": sorted(set(database)),
|
|
384
|
+
"payments": payments,
|
|
385
|
+
"sellsServices": bool(service_hits),
|
|
386
|
+
"serviceEvidence": service_hits,
|
|
387
|
+
"blog": blog,
|
|
388
|
+
"locales": sorted(locales),
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def recommendations(analysis: dict[str, object], available: list[str], installed: list[str]) -> list[dict[str, str]]:
|
|
393
|
+
result: list[dict[str, str]] = []
|
|
394
|
+
if "booking" in available and "booking" not in installed:
|
|
395
|
+
if analysis.get("sellsServices"):
|
|
396
|
+
notes = []
|
|
397
|
+
if not any(name in {"pg", "postgres", "@neondatabase/serverless"} for name in analysis.get("database", [])):
|
|
398
|
+
notes.append("it needs PostgreSQL, which this project does not use yet")
|
|
399
|
+
if analysis.get("auth"):
|
|
400
|
+
notes.append(f"the site already signs people in with {', '.join(analysis['auth'])}; MaggieDash keeps its own staff sign-in")
|
|
401
|
+
reason = "The site offers appointments or services (" + ", ".join(analysis.get("serviceEvidence", [])[:3]) + ")" + ("; " + "; ".join(notes) if notes else "")
|
|
402
|
+
result.append({"kind": "module", "module": "booking", "recommended": "yes", "reason": reason, "command": "maggie dash add booking --confirm"})
|
|
403
|
+
else:
|
|
404
|
+
result.append({"kind": "module", "module": "booking", "recommended": "no", "reason": "No appointments or bookable services found in src/; add it only if the business takes bookings.", "command": "maggie dash add booking --confirm"})
|
|
405
|
+
if analysis.get("blog"):
|
|
406
|
+
result.append({"kind": "skill", "skill": "maggie-blog", "recommended": "yes", "reason": "The site has a blog; manage posts from the dashboard Blog panel.", "command": "use the maggie-blog skill"})
|
|
407
|
+
result.append({"kind": "skill", "skill": "maggie-social-share", "recommended": "maybe", "reason": "Blog posts can be shared to social channels after publishing.", "command": "use the maggie-social-share skill"})
|
|
408
|
+
else:
|
|
409
|
+
result.append({"kind": "skill", "skill": "maggie-blog-bootstrap", "recommended": "maybe", "reason": "No blog found; add one only if the site will publish articles.", "command": "use the maggie-blog-bootstrap skill"})
|
|
410
|
+
if len(analysis.get("locales", [])) > 1:
|
|
411
|
+
result.append({"kind": "skill", "skill": "maggie-content-localization", "recommended": "yes", "reason": f"The site serves {', '.join(analysis['locales'])}; keep translations in the Languages panel.", "command": "use the maggie-content-localization skill"})
|
|
412
|
+
return result
|
|
413
|
+
|
|
414
|
+
|
|
291
415
|
def command_install_diff(source: Path, manifest: dict[str, object], target: Path, compare_dir: Path | None, force: bool, host_pairs: list[tuple[Path, Path, Path]] | None = None, host_framework: str | None = None) -> dict[str, object]:
|
|
292
416
|
pairs = manifest_file_pairs(source, manifest, target)
|
|
293
417
|
root_pairs = manifest_root_file_pairs(source, manifest, target.parents[1])
|
|
@@ -386,7 +510,16 @@ def command_install(args: argparse.Namespace) -> int:
|
|
|
386
510
|
raise RuntimeError("unsupported MaggieDash distribution schema")
|
|
387
511
|
if manifest.get("target") != "_maggie/admin" and args.target == "_maggie/admin":
|
|
388
512
|
raise RuntimeError("MaggieDash manifest target is not ./_maggie/admin")
|
|
513
|
+
previous = installed_state(root)
|
|
514
|
+
legacy = not module_names(manifest)
|
|
515
|
+
modules = resolve_modules(manifest, list(getattr(args, "with_modules", None) or []) + (module_names(manifest) if getattr(args, "all_modules", False) else []), [name for name in previous.get("modules", []) if name != CORE_MODULE])
|
|
516
|
+
module_specs = manifest.get("modules") if isinstance(manifest.get("modules"), dict) else {}
|
|
517
|
+
manifest = effective_manifest(manifest, modules)
|
|
389
518
|
host_pairs = manifest_host_file_pairs(source, manifest, root, host_framework)
|
|
519
|
+
for name in modules:
|
|
520
|
+
host_pairs += manifest_host_file_pairs(source, module_specs[name], root, host_framework, required=False)
|
|
521
|
+
analysis = analyse_host(root, host_framework)
|
|
522
|
+
advice = recommendations(analysis, module_names({"modules": module_specs}), modules)
|
|
390
523
|
compare_dir = None
|
|
391
524
|
if args.existing_dir:
|
|
392
525
|
compare_dir = Path(args.existing_dir).expanduser()
|
|
@@ -394,7 +527,9 @@ def command_install(args: argparse.Namespace) -> int:
|
|
|
394
527
|
compare_dir = root / compare_dir
|
|
395
528
|
compare_dir = compare_dir.resolve()
|
|
396
529
|
if args.dry_run or args.diff:
|
|
397
|
-
|
|
530
|
+
plan = command_install_diff(source, manifest, target, compare_dir, args.force, host_pairs, host_framework)
|
|
531
|
+
plan.update({"modules": [CORE_MODULE, *modules] if not legacy else ["all (release without modules)"], "availableModules": module_names({"modules": module_specs}), "host": analysis, "recommendations": advice})
|
|
532
|
+
emit(plan)
|
|
398
533
|
return 0
|
|
399
534
|
installed: list[str] = []
|
|
400
535
|
preserved: list[str] = []
|
|
@@ -471,18 +606,39 @@ def command_install(args: argparse.Namespace) -> int:
|
|
|
471
606
|
"hostPreserved": sorted(set(host_preserved)),
|
|
472
607
|
"hostUpdated": sorted(set(host_updated)),
|
|
473
608
|
"hostWarnings": sorted(set(host_warnings)),
|
|
609
|
+
"modules": [CORE_MODULE, *modules] if not legacy else ["legacy-all"],
|
|
474
610
|
"updatedAt": utc_now(),
|
|
475
611
|
}
|
|
476
612
|
state_path = root / ".maggie" / "dash-install.json"
|
|
477
613
|
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
478
614
|
state_path.write_text(json.dumps(state, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
479
|
-
|
|
615
|
+
notices = ["this MaggieDash release has no module split, so its Booking workspace was installed with the core; use MaggieDash 0.2.14 or later to install the core first"] if legacy and manifest.get("workspaces") else []
|
|
616
|
+
emit({"status": "needs-attention" if host_warnings else "installed", "version": manifest.get("version"), "modules": state["modules"], "notices": notices, "recommendations": advice, "next": "Add a recommended module with `maggie dash add <module> --confirm`; nothing optional is installed without that step." if advice and not legacy else None, "target": str(target), "hostFramework": host_framework, "hostInstalled": len(set(host_installed)), "hostPreserved": len(set(host_preserved)), "hostUpdated": len(set(host_updated)), "hostWarnings": host_warnings, "installed": len(set(installed)), "preserved": len(set(preserved)), "state": str(state_path)})
|
|
480
617
|
return 1 if host_warnings else 0
|
|
481
618
|
finally:
|
|
482
619
|
if temporary is not None:
|
|
483
620
|
temporary.cleanup()
|
|
484
621
|
|
|
485
622
|
|
|
623
|
+
def command_add(args: argparse.Namespace) -> int:
|
|
624
|
+
"""Install one optional module next to an existing (or new) core install."""
|
|
625
|
+
args.with_modules = list(args.modules)
|
|
626
|
+
args.all_modules = False
|
|
627
|
+
return command_install(args)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def command_recommend(args: argparse.Namespace) -> int:
|
|
631
|
+
root = project_root(args)
|
|
632
|
+
framework = detect_host_framework(root, args.host)
|
|
633
|
+
state = installed_state(root)
|
|
634
|
+
installed = [name for name in state.get("modules", []) if name != CORE_MODULE]
|
|
635
|
+
analysis = analyse_host(root, framework)
|
|
636
|
+
# The module list comes from the installed release when known; Booking is
|
|
637
|
+
# the optional code module MaggieDash ships today.
|
|
638
|
+
emit({"status": "recommendations", "installed": state.get("modules", []), "host": analysis, "recommendations": recommendations(analysis, ["booking"], installed)})
|
|
639
|
+
return 0
|
|
640
|
+
|
|
641
|
+
|
|
486
642
|
def command_init(args: argparse.Namespace) -> int:
|
|
487
643
|
require_confirm(args)
|
|
488
644
|
root = project_root(args)
|
|
@@ -832,8 +988,22 @@ def parser() -> argparse.ArgumentParser:
|
|
|
832
988
|
install.add_argument("--dry-run", action="store_true", help="show the install plan without writing files")
|
|
833
989
|
install.add_argument("--diff", action="store_true", help="show the install plan and compare with an existing workspace")
|
|
834
990
|
install.add_argument("--existing-dir", help="existing dashboard source directory to compare during --diff")
|
|
991
|
+
install.add_argument("--with", dest="with_modules", action="append", default=[], metavar="MODULE", help="also install an optional module (repeatable), e.g. --with booking")
|
|
992
|
+
install.add_argument("--all-modules", action="store_true", help="install every optional module (the pre-0.2.14 behaviour)")
|
|
835
993
|
install.add_argument("--confirm", action="store_true")
|
|
836
994
|
install.set_defaults(func=command_install)
|
|
995
|
+
add = sub.add_parser("add", help="add an optional MaggieDash module (e.g. booking) to an installed core")
|
|
996
|
+
add.add_argument("modules", nargs="+", metavar="MODULE")
|
|
997
|
+
for flag, options in (("--project", {"default": "."}), ("--source", {"help": "local checkout or Git URL; defaults to MaggieDash repository"}), ("--ref", {"default": "main"}), ("--target", {"default": "_maggie/admin"}), ("--existing-dir", {})):
|
|
998
|
+
add.add_argument(flag, **options)
|
|
999
|
+
add.add_argument("--host", choices=["auto", "astro", "none"], default="auto")
|
|
1000
|
+
for flag in ("--force", "--dry-run", "--diff", "--confirm"):
|
|
1001
|
+
add.add_argument(flag, action="store_true")
|
|
1002
|
+
add.set_defaults(func=command_add)
|
|
1003
|
+
recommend = sub.add_parser("recommend", help="analyse the host project and recommend optional modules and skills")
|
|
1004
|
+
recommend.add_argument("--project", default=".")
|
|
1005
|
+
recommend.add_argument("--host", choices=["auto", "astro", "none"], default="auto")
|
|
1006
|
+
recommend.set_defaults(func=command_recommend)
|
|
837
1007
|
status = sub.add_parser("status", help="inspect project and documents")
|
|
838
1008
|
status.add_argument("--project", default=".")
|
|
839
1009
|
status.add_argument("--project-id")
|