naro-cli 0.25.0 → 0.26.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.
@@ -7,7 +7,7 @@ import {
7
7
  import { existsSync as existsSync10 } from "fs";
8
8
  import { mkdtemp as mkdtemp2, readFile as readFile15, rm as rm5 } from "fs/promises";
9
9
  import { tmpdir as tmpdir2 } from "os";
10
- import { basename as basename5, dirname as dirname10, join as join32, resolve as resolve14 } from "path";
10
+ import { basename as basename5, dirname as dirname10, join as join31, resolve as resolve14 } from "path";
11
11
  import { fileURLToPath as fileURLToPath4 } from "url";
12
12
 
13
13
  // ../../packages/build/src/artifact-upload.ts
@@ -289,7 +289,7 @@ function hashAsset(data, contentType2) {
289
289
  // ../../packages/build/src/local-build-executor.ts
290
290
  import { access as access8, mkdtemp, readFile as readFile13, rm as rm3 } from "fs/promises";
291
291
  import { tmpdir } from "os";
292
- import { dirname as dirname4, join as join18, resolve as resolve7 } from "path";
292
+ import { dirname as dirname4, join as join17, resolve as resolve7 } from "path";
293
293
 
294
294
  // ../../packages/core/src/route-patterns.ts
295
295
  var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
@@ -317,6 +317,1918 @@ var IDENTIFIER = new RegExp(
317
317
  "g"
318
318
  );
319
319
 
320
+ // ../../packages/core/src/capabilities.ts
321
+ var REASON_CODES = {
322
+ "secret-value": {
323
+ clients: ["mcp"],
324
+ why: "\uC2DC\uD06C\uB9BF \uAC12\uC744 \uBC1B\uAC70\uB098 \uB3CC\uB824\uC900\uB2E4. \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4 (apps/mcp/src/function-tools.ts:34)."
325
+ },
326
+ irreversible: {
327
+ clients: ["mcp"],
328
+ why: "\uB418\uB3CC\uB9B4 \uC218 \uC5C6\uACE0 \uD655\uC778\uC73C\uB85C\uB9CC \uB9C9\uC744 \uC218 \uC788\uB2E4 (project_cli_mcp_backlog #6)."
329
+ },
330
+ "privilege-escalation": {
331
+ clients: ["mcp"],
332
+ why: "\uAD8C\uD55C\xB7\uC5ED\uD560\xB7\uBA64\uBC84\uC2ED\uC744 \uBC14\uAFBC\uB2E4."
333
+ },
334
+ "payment-flow": {
335
+ clients: ["cli", "mcp"],
336
+ why: "\uCE74\uB4DC \uC815\uBCF4\uAC00 \uC9C0\uB098\uAC04\uB2E4. \uD638\uC2A4\uD305 \uACB0\uC81C\uCC3D\uC774 \uBC1B\uB294\uB2E4."
337
+ },
338
+ "browser-required": {
339
+ clients: ["cli", "mcp"],
340
+ why: "OAuth \uB9AC\uB2E4\uC774\uB809\uD2B8\uAC00 \uD544\uC694\uD558\uB2E4."
341
+ },
342
+ "local-filesystem": {
343
+ clients: ["ui"],
344
+ why: "\uB85C\uCEEC \uD30C\uC77C\uC744 \uC77D\uAC70\uB098 \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
345
+ },
346
+ "ui-affordance": {
347
+ clients: ["cli", "mcp"],
348
+ why: "\uD654\uBA74 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4."
349
+ },
350
+ "agent-native": {
351
+ clients: ["mcp"],
352
+ // "naro CLI로 할 수 있다"는 사유가 아니다 — 그러면 CLI에 있는 모든 기능을
353
+ // 이 코드로 뺄 수 있다(PR #423 리뷰). 컨트롤 플레인에 닿는 일이면 planned다.
354
+ why: "\uC774 \uCEF4\uD4E8\uD130\uC758 \uD30C\uC77C\uB9CC \uC4F0\uB294 \uC77C\uC774\uB2E4. \uC5D0\uC774\uC804\uD2B8\uB294 \uC81C \uB3C4\uAD6C\uB85C \uD30C\uC77C\uC744 \uC4F4\uB2E4 \u2014 MCP \uD234\uB85C \uAC10\uC2F8\uBA74 \uC4F0\uAE30 \uACBD\uB85C \uAC80\uC0AC\uB9CC \uB294\uB2E4."
355
+ },
356
+ "user-session": {
357
+ clients: ["mcp"],
358
+ why: "\uC0AC\uC6A9\uC790 \uC790\uC2E0\uC758 \uB85C\uADF8\uC778 \uC138\uC158\uC744 \uBC14\uAFBC\uB2E4. \uC0AC\uB78C\uC774 \uD130\uBBF8\uB110\uC5D0\uC11C \uD55C\uB2E4."
359
+ },
360
+ "dashboard-itself": {
361
+ clients: ["ui"],
362
+ why: "\uB300\uC2DC\uBCF4\uB4DC\uB85C \uAC00\uB294 \uAE38\uC744 \uC54C\uB824 \uC8FC\uB294 \uAE30\uB2A5\uC774\uB2E4. \uB300\uC2DC\uBCF4\uB4DC \uC548\uC5D0\uC11C\uB294 \uB73B\uC774 \uC5C6\uB2E4."
363
+ }
364
+ };
365
+ var CAPABILITIES = [
366
+ {
367
+ id: "auth-whoami",
368
+ domain: "auth",
369
+ title: "\uB0B4 \uACC4\uC815 \uD655\uC778",
370
+ ui: { kind: "built" },
371
+ cli: { kind: "built", commands: ["naro whoami"] },
372
+ mcp: { kind: "built", tools: ["whoami"] }
373
+ },
374
+ {
375
+ id: "auth-ci-token",
376
+ domain: "auth",
377
+ title: "CI \uD1A0\uD070 \uBC1C\uAE09",
378
+ ui: {
379
+ kind: "excluded",
380
+ reason: "local-filesystem",
381
+ note: "naro login\uC774 \uC774 \uCEF4\uD4E8\uD130\uC5D0 \uB0A8\uAE34 \uC138\uC158(~/.naro/credentials.json)\uC5D0\uC11C \uD1A0\uD070\uC744 \uAEBC\uB0B8\uB2E4. \uBE0C\uB77C\uC6B0\uC800 \uC138\uC158\uC5D0\uB294 \uADF8 \uD30C\uC77C\uC774 \uC5C6\uB2E4."
382
+ },
383
+ cli: { kind: "built", commands: ["naro token"] },
384
+ mcp: {
385
+ kind: "excluded",
386
+ reason: "secret-value",
387
+ note: "\uD1A0\uD070 \uC790\uCCB4\uAC00 \uC2DC\uD06C\uB9BF\uC774\uB77C \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
388
+ }
389
+ },
390
+ {
391
+ id: "auth-login",
392
+ domain: "auth",
393
+ title: "\uB85C\uADF8\uC778",
394
+ dashboard: "/login",
395
+ ui: { kind: "built" },
396
+ cli: { kind: "built", commands: ["naro login"] },
397
+ mcp: {
398
+ kind: "excluded",
399
+ reason: "browser-required",
400
+ note: "apps/mcp/src/tools.ts \u2014 \uC774 \uC11C\uBC84\uB294 \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4 \uC218 \uC5C6\uB2E4. \uC0AC\uC6A9\uC790\uAC00 \uD130\uBBF8\uB110\uC5D0\uC11C naro login\uC744 \uB3CC\uB9AC\uAC8C \uD55C\uB2E4."
401
+ }
402
+ },
403
+ {
404
+ id: "auth-logout",
405
+ domain: "auth",
406
+ title: "\uB85C\uADF8\uC544\uC6C3",
407
+ dashboard: null,
408
+ ui: { kind: "built" },
409
+ cli: { kind: "built", commands: ["naro logout"] },
410
+ mcp: {
411
+ kind: "excluded",
412
+ reason: "user-session",
413
+ note: "naro login \uC138\uC158\uC740 \uC0AC\uB78C\uC774 \uB2E4\uB8EC\uB2E4. 2026-11-03 \uACB0\uC815."
414
+ }
415
+ },
416
+ {
417
+ id: "team-list",
418
+ domain: "teams",
419
+ title: "\uD300 \uBAA9\uB85D",
420
+ routes: ["GET teams"],
421
+ ui: { kind: "built" },
422
+ cli: { kind: "built", commands: ["naro teams"] },
423
+ mcp: { kind: "built", tools: ["list_teams"] }
424
+ },
425
+ {
426
+ id: "team-member-list",
427
+ domain: "teams",
428
+ title: "\uBA64\uBC84 \uBAA9\uB85D",
429
+ routes: ["GET teams/[teamSlug]/members"],
430
+ ui: { kind: "built" },
431
+ cli: { kind: "built", commands: ["naro team members"] },
432
+ mcp: { kind: "built", tools: ["list_team_members"] }
433
+ },
434
+ {
435
+ id: "team-create",
436
+ domain: "teams",
437
+ title: "\uD300 \uC0DD\uC131",
438
+ routes: ["POST teams"],
439
+ ui: { kind: "built" },
440
+ cli: { kind: "built", commands: ["naro team create"] },
441
+ mcp: { kind: "built", tools: ["create_team"] }
442
+ },
443
+ {
444
+ id: "team-update",
445
+ domain: "teams",
446
+ title: "\uD300 \uC124\uC815 \uBCC0\uACBD",
447
+ routes: ["PATCH teams/[teamSlug]"],
448
+ ui: { kind: "built" },
449
+ cli: { kind: "built", commands: ["naro team set"] },
450
+ mcp: { kind: "built", tools: ["update_team"] }
451
+ },
452
+ {
453
+ id: "team-delete",
454
+ domain: "teams",
455
+ title: "\uD300 \uC0AD\uC81C",
456
+ routes: ["DELETE teams/[teamSlug]"],
457
+ dashboard: "/[teamSlug]/settings",
458
+ ui: { kind: "built" },
459
+ cli: { kind: "built", commands: ["naro team delete"] },
460
+ mcp: {
461
+ kind: "excluded",
462
+ reason: "irreversible",
463
+ note: "\uD300\uC758 \uD504\uB85C\uC81D\uD2B8\xB7\uB370\uC774\uD130\uAC00 \uD568\uAED8 \uC0AC\uB77C\uC9C4\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
464
+ }
465
+ },
466
+ {
467
+ id: "team-member-update",
468
+ domain: "teams",
469
+ title: "\uBA64\uBC84 \uC5ED\uD560 \uBCC0\uACBD",
470
+ routes: ["PATCH teams/[teamSlug]/members/[userId]"],
471
+ dashboard: "/[teamSlug]/settings",
472
+ ui: { kind: "built" },
473
+ cli: { kind: "built", commands: ["naro team members set"] },
474
+ mcp: {
475
+ kind: "excluded",
476
+ reason: "privilege-escalation",
477
+ note: "\uC5ED\uD560\uC744 \uBC14\uAFBC\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
478
+ }
479
+ },
480
+ {
481
+ id: "team-member-remove",
482
+ domain: "teams",
483
+ title: "\uBA64\uBC84 \uC81C\uAC70",
484
+ routes: ["DELETE teams/[teamSlug]/members/[userId]"],
485
+ dashboard: "/[teamSlug]/settings",
486
+ ui: { kind: "built" },
487
+ cli: { kind: "built", commands: ["naro team members remove"] },
488
+ mcp: {
489
+ kind: "excluded",
490
+ reason: "privilege-escalation",
491
+ note: "\uBA64\uBC84\uC2ED\uC744 \uBC14\uAFBC\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
492
+ }
493
+ },
494
+ {
495
+ id: "team-invite-list",
496
+ domain: "teams",
497
+ title: "\uCD08\uB300 \uBAA9\uB85D",
498
+ routes: ["GET teams/[teamSlug]/invites"],
499
+ ui: { kind: "built" },
500
+ cli: { kind: "built", commands: ["naro team invites"] },
501
+ mcp: { kind: "built", tools: ["list_team_invites"] }
502
+ },
503
+ {
504
+ id: "team-invite-create",
505
+ domain: "teams",
506
+ title: "\uCD08\uB300 \uBCF4\uB0B4\uAE30",
507
+ routes: ["POST teams/[teamSlug]/invites"],
508
+ dashboard: "/[teamSlug]/settings",
509
+ ui: { kind: "built" },
510
+ cli: { kind: "built", commands: ["naro team invites create"] },
511
+ mcp: {
512
+ kind: "excluded",
513
+ reason: "privilege-escalation",
514
+ note: "\uD300\uC5D0 \uC0AC\uB78C\uC744 \uB4E4\uC778\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
515
+ }
516
+ },
517
+ {
518
+ id: "team-invite-revoke",
519
+ domain: "teams",
520
+ title: "\uCD08\uB300 \uCDE8\uC18C",
521
+ routes: ["DELETE teams/[teamSlug]/invites/[inviteId]"],
522
+ ui: { kind: "built" },
523
+ cli: { kind: "built", commands: ["naro team invites revoke"] },
524
+ mcp: { kind: "built", tools: ["revoke_team_invite"] }
525
+ },
526
+ {
527
+ id: "team-usage",
528
+ domain: "teams",
529
+ title: "\uD300 \uC0AC\uC6A9\uB7C9",
530
+ routes: ["GET teams/[teamSlug]/usage"],
531
+ ui: { kind: "built" },
532
+ cli: { kind: "built", commands: ["naro usage"] },
533
+ mcp: { kind: "built", tools: ["get_team_usage"] }
534
+ },
535
+ {
536
+ id: "billing-view",
537
+ domain: "billing",
538
+ title: "\uD50C\uB79C\xB7\uAD6C\uB3C5 \uC870\uD68C",
539
+ routes: ["GET teams/[teamSlug]/billing"],
540
+ ui: { kind: "built" },
541
+ cli: { kind: "built", commands: ["naro billing"] },
542
+ mcp: { kind: "built", tools: ["get_team_billing"] }
543
+ },
544
+ {
545
+ id: "billing-checkout",
546
+ domain: "billing",
547
+ title: "\uCCB4\uD06C\uC544\uC6C3",
548
+ routes: ["POST teams/[teamSlug]/billing/checkout"],
549
+ dashboard: "/[teamSlug]/usage",
550
+ ui: { kind: "built" },
551
+ cli: {
552
+ kind: "excluded",
553
+ reason: "payment-flow",
554
+ note: "\uCE74\uB4DC \uC815\uBCF4\uB294 Paddle\uC758 \uD638\uC2A4\uD305 \uACB0\uC81C\uCC3D\uC774 \uBC1B\uB294\uB2E4."
555
+ },
556
+ mcp: {
557
+ kind: "excluded",
558
+ reason: "payment-flow",
559
+ note: "\uCE74\uB4DC \uC815\uBCF4\uB294 Paddle\uC758 \uD638\uC2A4\uD305 \uACB0\uC81C\uCC3D\uC774 \uBC1B\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
560
+ }
561
+ },
562
+ {
563
+ id: "billing-portal",
564
+ domain: "billing",
565
+ title: "\uACB0\uC81C \uD3EC\uD138",
566
+ routes: ["POST teams/[teamSlug]/billing/portal"],
567
+ dashboard: "/[teamSlug]/usage",
568
+ ui: { kind: "built" },
569
+ cli: {
570
+ kind: "excluded",
571
+ reason: "payment-flow",
572
+ note: "\uACB0\uC81C \uC218\uB2E8 \uBCC0\uACBD\uC740 Paddle \uD3EC\uD138\uC5D0\uC11C \uD55C\uB2E4."
573
+ },
574
+ mcp: {
575
+ kind: "excluded",
576
+ reason: "payment-flow",
577
+ note: "\uACB0\uC81C \uC218\uB2E8 \uBCC0\uACBD\uC740 Paddle \uD3EC\uD138\uC5D0\uC11C \uD55C\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
578
+ }
579
+ },
580
+ {
581
+ id: "project-list",
582
+ domain: "projects",
583
+ title: "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D",
584
+ routes: ["GET projects"],
585
+ ui: { kind: "built" },
586
+ cli: { kind: "built", commands: ["naro projects"] },
587
+ mcp: { kind: "built", tools: ["list_projects"] }
588
+ },
589
+ {
590
+ id: "project-create",
591
+ domain: "projects",
592
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC0DD\uC131",
593
+ routes: ["POST projects"],
594
+ ui: { kind: "built" },
595
+ // naro deploy가 링크가 없을 때 만든다(POST cli/deployments).
596
+ cli: { kind: "built" },
597
+ // deploy가 링크가 없을 때 만든다(POST cli/deployments).
598
+ mcp: { kind: "built" }
599
+ },
600
+ {
601
+ id: "project-get",
602
+ domain: "projects",
603
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC870\uD68C",
604
+ routes: ["GET projects/[projectId]"],
605
+ ui: { kind: "built" },
606
+ cli: { kind: "built", commands: ["naro project inspect"] },
607
+ mcp: { kind: "built", tools: ["get_project"] }
608
+ },
609
+ {
610
+ id: "project-link",
611
+ domain: "projects",
612
+ title: "\uB514\uB809\uD130\uB9AC\uB97C \uD504\uB85C\uC81D\uD2B8\uC5D0 \uB9C1\uD06C",
613
+ ui: {
614
+ kind: "excluded",
615
+ reason: "local-filesystem",
616
+ note: ".naro/project.json\uC5D0 \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
617
+ },
618
+ cli: { kind: "built", commands: ["naro link", "naro init"] },
619
+ mcp: { kind: "built", tools: ["link_project"] }
620
+ },
621
+ {
622
+ id: "project-unlink",
623
+ domain: "projects",
624
+ title: "\uB9C1\uD06C \uD574\uC81C",
625
+ ui: {
626
+ kind: "excluded",
627
+ reason: "local-filesystem",
628
+ note: ".naro/\uB97C \uC9C0\uC6B4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
629
+ },
630
+ cli: { kind: "built", commands: ["naro unlink"] },
631
+ mcp: {
632
+ kind: "excluded",
633
+ reason: "agent-native",
634
+ note: ".naro/\uB97C \uC9C0\uC6B0\uB294 \uC77C\uC774\uB2E4. \uC9DD\uC778 link_project\uAC00 \uD234\uC778 \uAC83\uC740 \uD504\uB85C\uC81D\uD2B8\uB97C \uACE0\uB974\uB824\uBA74 \uCEE8\uD2B8\uB864 \uD50C\uB808\uC778\uC744 \uC77D\uC5B4\uC57C \uD574\uC11C\uB2E4 \u2014 unlink\uB294 \uD30C\uC77C\uB9CC \uC9C0\uC6B4\uB2E4. 2026-11-03 \uACB0\uC815."
635
+ }
636
+ },
637
+ {
638
+ id: "project-update",
639
+ domain: "projects",
640
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815 \uBCC0\uACBD(\uD504\uB808\uC784\uC6CC\uD06C\xB7\uC11C\uBE0C\uB3C4\uBA54\uC778)",
641
+ routes: ["PATCH projects/[projectId]"],
642
+ ui: { kind: "built" },
643
+ cli: { kind: "built", commands: ["naro project set"] },
644
+ // 서브도메인은 빼고(PR #433 리뷰): 옛 이름이 바로 비어 누구나 가져갈 수 있어
645
+ // 북마크·OAuth 리다이렉트가 남의 앱으로 간다. 대시보드와 CLI(먼저 묻는다)만.
646
+ mcp: { kind: "built", tools: ["update_project"] }
647
+ },
648
+ {
649
+ id: "project-delete",
650
+ domain: "projects",
651
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C",
652
+ routes: ["DELETE projects/[projectId]"],
653
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
654
+ ui: { kind: "built" },
655
+ cli: { kind: "built", commands: ["naro project delete"] },
656
+ mcp: {
657
+ kind: "excluded",
658
+ reason: "irreversible",
659
+ note: "\uC6CC\uCEE4\xB7D1\xB7R2\xB7\uB3C4\uBA54\uC778\xB7\uBC30\uD3EC \uC774\uB825\uC774 cascade\uB85C \uB0A0\uC544\uAC04\uB2E4. \uC5D0\uC774\uC804\uD2B8\uB294 \uD504\uB86C\uD504\uD2B8 \uC778\uC81D\uC158\uC5D0 \uB178\uCD9C\uB418\uBBC0\uB85C \uD655\uC778\uC73C\uB85C \uB9C9\uC744 \uC218 \uC5C6\uB2E4(project_cli_mcp_backlog #6)."
660
+ }
661
+ },
662
+ {
663
+ id: "project-subdomain-check",
664
+ domain: "projects",
665
+ title: "\uC11C\uBE0C\uB3C4\uBA54\uC778 \uC0AC\uC6A9 \uAC00\uB2A5 \uC5EC\uBD80",
666
+ routes: ["GET subdomains/availability"],
667
+ ui: { kind: "built" },
668
+ cli: { kind: "built", commands: ["naro project check"] },
669
+ mcp: { kind: "built", tools: ["check_subdomain"] }
670
+ },
671
+ {
672
+ id: "project-access-list",
673
+ domain: "projects",
674
+ title: "\uBA64\uBC84\uBCC4 \uD504\uB85C\uC81D\uD2B8 \uC811\uADFC \uC870\uD68C",
675
+ routes: ["GET projects/[projectId]/access"],
676
+ ui: { kind: "built" },
677
+ cli: { kind: "built", commands: ["naro project access"] },
678
+ mcp: { kind: "built", tools: ["list_project_access"] }
679
+ },
680
+ {
681
+ id: "project-access-grant",
682
+ domain: "projects",
683
+ title: "\uBA64\uBC84\uC5D0\uAC8C \uD504\uB85C\uC81D\uD2B8 \uC811\uADFC \uBD80\uC5EC",
684
+ routes: ["PUT projects/[projectId]/access"],
685
+ dashboard: "/[teamSlug]/projects/[projectSlug]/access",
686
+ ui: { kind: "built" },
687
+ cli: { kind: "built", commands: ["naro project access grant"] },
688
+ mcp: {
689
+ kind: "excluded",
690
+ reason: "privilege-escalation",
691
+ note: "\uBA64\uBC84\uC758 \uD504\uB85C\uC81D\uD2B8 \uAD8C\uD55C\uC744 \uBC14\uAFBC\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
692
+ }
693
+ },
694
+ {
695
+ id: "project-access-revoke",
696
+ domain: "projects",
697
+ title: "\uBA64\uBC84\uC758 \uD504\uB85C\uC81D\uD2B8 \uC811\uADFC \uD68C\uC218",
698
+ routes: ["DELETE projects/[projectId]/access"],
699
+ dashboard: "/[teamSlug]/projects/[projectSlug]/access",
700
+ ui: { kind: "built" },
701
+ cli: { kind: "built", commands: ["naro project access revoke"] },
702
+ mcp: {
703
+ kind: "excluded",
704
+ reason: "privilege-escalation",
705
+ note: "\uBA64\uBC84\uC758 \uD504\uB85C\uC81D\uD2B8 \uAD8C\uD55C\uC744 \uBC14\uAFBC\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
706
+ }
707
+ },
708
+ {
709
+ id: "deployment-list",
710
+ domain: "deployments",
711
+ title: "\uBC30\uD3EC \uBAA9\uB85D",
712
+ routes: ["GET projects/[projectId]/deployments"],
713
+ ui: { kind: "built" },
714
+ cli: { kind: "built", commands: ["naro deployments"] },
715
+ mcp: { kind: "built", tools: ["list_deployments"] }
716
+ },
717
+ {
718
+ id: "deployment-create",
719
+ domain: "deployments",
720
+ title: "\uBE0C\uB79C\uCE58\uC5D0\uC11C \uC0C8\uB85C \uBC30\uD3EC",
721
+ routes: ["POST projects/[projectId]/deployments"],
722
+ ui: { kind: "built" },
723
+ cli: { kind: "built", commands: ["naro deployments create"] },
724
+ mcp: { kind: "built", tools: ["deploy_from_git"] }
725
+ },
726
+ {
727
+ id: "deployment-local",
728
+ domain: "deployments",
729
+ title: "\uB85C\uCEEC\uC5D0\uC11C \uBE4C\uB4DC\uD574 \uBC30\uD3EC",
730
+ routes: ["POST cli/deployments"],
731
+ ui: {
732
+ kind: "excluded",
733
+ reason: "local-filesystem",
734
+ note: "\uB85C\uCEEC \uC791\uC5C5 \uD2B8\uB9AC\uC5D0\uC11C \uBE4C\uB4DC\uD55C \uC0B0\uCD9C\uBB3C\uC744 \uC62C\uB9B0\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uC5D0\uB294 \uBE4C\uB4DC\uD560 \uC791\uC5C5 \uD2B8\uB9AC\uAC00 \uC5C6\uB2E4."
735
+ },
736
+ cli: { kind: "built", commands: ["naro deploy"] },
737
+ mcp: { kind: "built", tools: ["deploy"] }
738
+ },
739
+ {
740
+ id: "deployment-get",
741
+ domain: "deployments",
742
+ title: "\uBC30\uD3EC \uC0C1\uC138",
743
+ routes: ["GET deployments/[deploymentId]"],
744
+ ui: { kind: "built" },
745
+ cli: { kind: "built", commands: ["naro inspect"] },
746
+ mcp: { kind: "built", tools: ["get_deployment", "get_build_logs"] }
747
+ },
748
+ {
749
+ id: "deployment-cancel",
750
+ domain: "deployments",
751
+ title: "\uBE4C\uB4DC \uCDE8\uC18C",
752
+ routes: ["POST projects/[projectId]/deployments/[deploymentId]/cancel"],
753
+ ui: { kind: "built" },
754
+ cli: { kind: "built", commands: ["naro cancel"] },
755
+ mcp: { kind: "built", tools: ["cancel_deployment"] }
756
+ },
757
+ {
758
+ id: "deployment-promote-preview",
759
+ domain: "deployments",
760
+ title: "\uC2B9\uACA9 \uBBF8\uB9AC\uBCF4\uAE30",
761
+ routes: ["GET projects/[projectId]/deployments/[deploymentId]/promote"],
762
+ ui: { kind: "built" },
763
+ // naro promote가 묻기 전에 무엇을 할지 보여 준다(client.deployments.promoteCheck).
764
+ cli: { kind: "built" },
765
+ mcp: { kind: "built", tools: ["check_promotion"] }
766
+ },
767
+ {
768
+ id: "deployment-promote",
769
+ domain: "deployments",
770
+ title: "\uD504\uB85C\uB355\uC158\xB7\uD658\uACBD\uC73C\uB85C \uC2B9\uACA9(\uB864\uBC31 \uD3EC\uD568)",
771
+ routes: ["POST projects/[projectId]/deployments/[deploymentId]/promote"],
772
+ ui: { kind: "built" },
773
+ cli: { kind: "built", commands: ["naro promote", "naro rollback"] },
774
+ mcp: {
775
+ kind: "built",
776
+ tools: ["promote_deployment", "rollback_deployment"]
777
+ }
778
+ },
779
+ {
780
+ id: "git-app-install",
781
+ domain: "git",
782
+ title: "GitHub App \uC124\uCE58",
783
+ routes: ["GET github/authorize", "GET github/install", "GET github/setup"],
784
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
785
+ ui: { kind: "built" },
786
+ cli: {
787
+ kind: "excluded",
788
+ reason: "browser-required",
789
+ note: "GitHub App \uC124\uCE58\uB294 OAuth \uB9AC\uB2E4\uC774\uB809\uD2B8\uB85C \uB05D\uB09C\uB2E4. naro git connect\uAC00 \uC124\uCE58 URL\uC744 \uCC0D\uC5B4 \uC900\uB2E4."
790
+ },
791
+ mcp: {
792
+ kind: "excluded",
793
+ reason: "browser-required",
794
+ note: "apps/mcp/src/tools.ts\uC758 connect_git_repository \u2014 \uC124\uCE58 URL\uC744 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC8FC\uACE0 \uBE0C\uB77C\uC6B0\uC800\uB294 \uC9C1\uC811 \uC5F4\uC9C0 \uC54A\uB294\uB2E4."
795
+ }
796
+ },
797
+ {
798
+ id: "git-repository-list",
799
+ domain: "git",
800
+ title: "GitHub \uC800\uC7A5\uC18C \uBAA9\uB85D",
801
+ routes: ["GET github/repositories"],
802
+ ui: { kind: "built" },
803
+ // naro git connect가 저장소를 고를 때 읽는다(client.github.listRepositories).
804
+ cli: { kind: "built" },
805
+ mcp: { kind: "built", tools: ["list_git_repositories"] }
806
+ },
807
+ {
808
+ id: "git-connect",
809
+ domain: "git",
810
+ title: "\uC800\uC7A5\uC18C \uC5F0\uACB0",
811
+ routes: ["POST projects/[projectId]/git"],
812
+ ui: { kind: "built" },
813
+ cli: { kind: "built", commands: ["naro git connect"] },
814
+ mcp: { kind: "built", tools: ["connect_git_repository"] }
815
+ },
816
+ {
817
+ id: "git-disconnect",
818
+ domain: "git",
819
+ title: "\uC800\uC7A5\uC18C \uC5F0\uACB0 \uD574\uC81C",
820
+ routes: ["DELETE projects/[projectId]/git"],
821
+ ui: { kind: "built" },
822
+ cli: { kind: "built", commands: ["naro git disconnect"] },
823
+ mcp: { kind: "built", tools: ["disconnect_git_repository"] }
824
+ },
825
+ {
826
+ id: "env-list",
827
+ domain: "env",
828
+ title: "\uD658\uACBD\uBCC0\uC218 \uBAA9\uB85D",
829
+ routes: ["GET projects/[projectId]/env"],
830
+ ui: { kind: "built" },
831
+ cli: { kind: "built", commands: ["naro env list"] },
832
+ mcp: { kind: "built", tools: ["list_environment_variables"] }
833
+ },
834
+ {
835
+ id: "env-set",
836
+ domain: "env",
837
+ title: "\uD658\uACBD\uBCC0\uC218 \uCD94\uAC00\xB7\uC218\uC815",
838
+ routes: ["POST projects/[projectId]/env"],
839
+ ui: { kind: "built" },
840
+ cli: { kind: "built", commands: ["naro env add", "naro env import"] },
841
+ mcp: { kind: "built", tools: ["set_environment_variable"] }
842
+ },
843
+ {
844
+ id: "env-remove",
845
+ domain: "env",
846
+ title: "\uD658\uACBD\uBCC0\uC218 \uC81C\uAC70",
847
+ routes: ["DELETE projects/[projectId]/env/[envVarId]"],
848
+ ui: { kind: "built" },
849
+ cli: { kind: "built", commands: ["naro env remove"] },
850
+ mcp: { kind: "built", tools: ["remove_environment_variable"] }
851
+ },
852
+ // 앱 메일의 Resend 연결(spec 2026-11-04 resend-email-integration). 연결은
853
+ // env 두 행이라 env 도메인에 둔다. CLI·MCP는 그 스펙의 PR 2다.
854
+ {
855
+ id: "email-status",
856
+ domain: "env",
857
+ title: "Resend \uC5F0\uACB0 \uBCF4\uAE30",
858
+ routes: ["GET projects/[projectId]/email"],
859
+ ui: { kind: "built" },
860
+ cli: { kind: "planned", note: "naro email status \u2014 \uC2A4\uD399 \xA713 PR 2." },
861
+ mcp: { kind: "planned", note: "get_email \u2014 \uC2A4\uD399 \xA713 PR 2." }
862
+ },
863
+ {
864
+ id: "email-connect",
865
+ domain: "env",
866
+ title: "Resend \uC5F0\uACB0\xB7\uBC14\uAFB8\uAE30",
867
+ routes: ["PUT projects/[projectId]/email"],
868
+ dashboard: "/[teamSlug]/projects/[projectSlug]/email",
869
+ ui: { kind: "built" },
870
+ cli: {
871
+ kind: "planned",
872
+ note: "naro email connect --api-key-stdin \u2014 \uC2A4\uD399 \xA713 PR 2."
873
+ },
874
+ mcp: {
875
+ kind: "excluded",
876
+ reason: "secret-value",
877
+ note: "Resend API \uD0A4\uB97C \uBC1B\uB294\uB2E4. \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4 \u2014 get_email\uC774 naro email connect\uB098 \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC548\uB0B4\uD55C\uB2E4(\uC2A4\uD399 \xA73-8)."
878
+ }
879
+ },
880
+ {
881
+ id: "email-disconnect",
882
+ domain: "env",
883
+ title: "Resend \uC5F0\uACB0 \uB04A\uAE30",
884
+ routes: ["DELETE projects/[projectId]/email"],
885
+ ui: { kind: "built" },
886
+ cli: { kind: "planned", note: "naro email disconnect \u2014 \uC2A4\uD399 \xA713 PR 2." },
887
+ mcp: { kind: "planned", note: "disconnect_email \u2014 \uC2A4\uD399 \xA713 PR 2." }
888
+ },
889
+ {
890
+ id: "env-pull",
891
+ domain: "env",
892
+ title: "\uD658\uACBD\uBCC0\uC218\uB97C \uB85C\uCEEC\uB85C \uB0B4\uB9AC\uAE30",
893
+ ui: {
894
+ kind: "excluded",
895
+ reason: "local-filesystem",
896
+ note: ".env.local\uC5D0 \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
897
+ },
898
+ cli: { kind: "built", commands: ["naro env pull"] },
899
+ mcp: {
900
+ kind: "excluded",
901
+ reason: "secret-value",
902
+ note: "\uBCF5\uD638\uD654\uD55C \uAC12\uC744 .env.local\uC5D0 \uC4F4\uB2E4. list_environment_variables\uB294 \uAC12\uC744 \uAC00\uB9B0\uB2E4. 2026-11-03 \uACB0\uC815."
903
+ }
904
+ },
905
+ {
906
+ id: "supabase-connect",
907
+ domain: "env",
908
+ title: "\uAE30\uC874 Supabase \uD504\uB85C\uC81D\uD2B8 \uC5F0\uACB0\xB7\uD0A4 \uB2E4\uC2DC \uBC1B\uAE30",
909
+ routes: [
910
+ "GET supabase/authorize",
911
+ "GET supabase/callback",
912
+ "GET projects/[projectId]/supabase/projects",
913
+ "PUT projects/[projectId]/supabase"
914
+ ],
915
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
916
+ ui: { kind: "built" },
917
+ cli: {
918
+ kind: "excluded",
919
+ reason: "browser-required",
920
+ note: "Supabase OAuth \uB9AC\uB2E4\uC774\uB809\uD2B8\uB85C \uB05D\uB09C\uB2E4. \uD1A0\uD070\uC740 10\uBD84 \uCFE0\uD0A4\uC5D0\uB9CC \uC788\uB2E4(\uC124\uACC4 2026-11-04 \xA72.1)."
921
+ },
922
+ mcp: {
923
+ kind: "excluded",
924
+ reason: "browser-required",
925
+ note: "Supabase OAuth \uB9AC\uB2E4\uC774\uB809\uD2B8\uB85C \uB05D\uB09C\uB2E4. \uB300\uC2DC\uBCF4\uB4DC\uC758 \uD504\uB85C\uC81D\uD2B8 \uC124\uC815 \uD0ED\uC5D0\uC11C \uC5F0\uACB0\uD55C\uB2E4."
926
+ }
927
+ },
928
+ {
929
+ id: "supabase-link-list",
930
+ domain: "env",
931
+ title: "Supabase \uC5F0\uACB0 \uBCF4\uAE30",
932
+ routes: ["GET projects/[projectId]/supabase"],
933
+ ui: { kind: "built" },
934
+ cli: { kind: "built", commands: ["naro supabase"] },
935
+ mcp: { kind: "built", tools: ["list_supabase_links"] }
936
+ },
937
+ {
938
+ id: "supabase-disconnect",
939
+ domain: "env",
940
+ title: "Supabase \uC5F0\uACB0 \uD574\uC81C",
941
+ routes: ["DELETE projects/[projectId]/supabase"],
942
+ ui: { kind: "built" },
943
+ cli: { kind: "built", commands: ["naro supabase disconnect"] },
944
+ mcp: { kind: "built", tools: ["disconnect_supabase"] }
945
+ },
946
+ {
947
+ id: "environment-list",
948
+ domain: "environments",
949
+ title: "\uD658\uACBD \uBAA9\uB85D",
950
+ routes: ["GET projects/[projectId]/environments"],
951
+ ui: { kind: "built" },
952
+ cli: { kind: "built", commands: ["naro environments"] },
953
+ mcp: { kind: "built", tools: ["list_environments"] }
954
+ },
955
+ {
956
+ id: "environment-create",
957
+ domain: "environments",
958
+ title: "\uD658\uACBD \uB9CC\uB4E4\uAE30",
959
+ routes: ["POST projects/[projectId]/environments"],
960
+ ui: { kind: "built" },
961
+ cli: { kind: "built", commands: ["naro environments create"] },
962
+ mcp: { kind: "built", tools: ["create_environment"] }
963
+ },
964
+ {
965
+ id: "environment-update",
966
+ domain: "environments",
967
+ title: "\uD658\uACBD \uC124\uC815 \uBCC0\uACBD(\uBE0C\uB79C\uCE58\xB7\uBCF4\uD638)",
968
+ routes: ["PATCH projects/[projectId]/environments/[environmentId]"],
969
+ ui: { kind: "built" },
970
+ cli: { kind: "built", commands: ["naro environments set"] },
971
+ mcp: { kind: "built", tools: ["update_environment"] }
972
+ },
973
+ {
974
+ id: "environment-delete",
975
+ domain: "environments",
976
+ title: "\uD658\uACBD \uC0AD\uC81C",
977
+ routes: ["DELETE projects/[projectId]/environments/[environmentId]"],
978
+ ui: { kind: "built" },
979
+ cli: { kind: "built", commands: ["naro environments delete"] },
980
+ mcp: { kind: "built", tools: ["delete_environment"] }
981
+ },
982
+ {
983
+ id: "environment-provision",
984
+ domain: "environments",
985
+ title: "\uD658\uACBD \uB370\uC774\uD130 \uD504\uB85C\uBE44\uC800\uB2DD",
986
+ routes: [
987
+ "POST projects/[projectId]/environments/[environmentId]/provision"
988
+ ],
989
+ ui: { kind: "built" },
990
+ cli: { kind: "built", commands: ["naro environments provision"] },
991
+ mcp: { kind: "built", tools: ["provision_environment"] }
992
+ },
993
+ {
994
+ id: "domain-list",
995
+ domain: "domains",
996
+ title: "\uB3C4\uBA54\uC778 \uBAA9\uB85D",
997
+ routes: ["GET projects/[projectId]/domains"],
998
+ ui: { kind: "built" },
999
+ cli: { kind: "built", commands: ["naro domains list"] },
1000
+ mcp: { kind: "built", tools: ["list_domains"] }
1001
+ },
1002
+ {
1003
+ id: "domain-add",
1004
+ domain: "domains",
1005
+ title: "\uB3C4\uBA54\uC778 \uCD94\uAC00",
1006
+ routes: ["POST projects/[projectId]/domains"],
1007
+ ui: { kind: "built" },
1008
+ cli: { kind: "built", commands: ["naro domains add"] },
1009
+ mcp: { kind: "built", tools: ["add_domain"] }
1010
+ },
1011
+ {
1012
+ id: "domain-refresh",
1013
+ domain: "domains",
1014
+ title: "\uB3C4\uBA54\uC778 \uC0C1\uD0DC \uC0C8\uB85C\uACE0\uCE68",
1015
+ routes: ["PATCH projects/[projectId]/domains/[domainId]"],
1016
+ ui: { kind: "built" },
1017
+ // naro domains verify가 Cloudflare 재확인까지 해서 refresh(저장된 상태 다시 읽기)의
1018
+ // 상위다. 같은 PATCH는 부르지 않는다.
1019
+ cli: { kind: "built" },
1020
+ // verify_domain이 같은 일을 더 한다(재확인 + DNS 점검).
1021
+ mcp: { kind: "built" }
1022
+ },
1023
+ {
1024
+ id: "domain-remove",
1025
+ domain: "domains",
1026
+ title: "\uB3C4\uBA54\uC778 \uC81C\uAC70",
1027
+ routes: ["DELETE projects/[projectId]/domains/[domainId]"],
1028
+ ui: { kind: "built" },
1029
+ cli: { kind: "built", commands: ["naro domains remove"] },
1030
+ mcp: { kind: "built", tools: ["remove_domain"] }
1031
+ },
1032
+ {
1033
+ id: "domain-verify",
1034
+ domain: "domains",
1035
+ title: "DNS \uC7AC\uAC80\uC99D",
1036
+ routes: ["POST projects/[projectId]/domains/[domainId]/verify"],
1037
+ ui: { kind: "built" },
1038
+ cli: { kind: "built", commands: ["naro domains verify"] },
1039
+ mcp: { kind: "built", tools: ["verify_domain"] }
1040
+ },
1041
+ {
1042
+ id: "domain-claim-list",
1043
+ domain: "domains",
1044
+ title: "\uB2E4\uB978 \uD504\uB85C\uC81D\uD2B8\uAC00 \uAC00\uC9C4 \uB3C4\uBA54\uC778\uC758 \uC18C\uC720\uAD8C \uD655\uC778 \uBAA9\uB85D",
1045
+ routes: ["GET projects/[projectId]/domain-claims"],
1046
+ ui: { kind: "built" },
1047
+ // naro domains verify가 소유권 확인을 함께 읽는다(client.domains.listClaims).
1048
+ cli: { kind: "built" },
1049
+ // verify_domain이 소유권 확인을 함께 읽는다(client.domains.listClaims).
1050
+ mcp: { kind: "built" }
1051
+ },
1052
+ {
1053
+ id: "domain-claim-verify",
1054
+ domain: "domains",
1055
+ title: "\uB3C4\uBA54\uC778 \uC18C\uC720\uAD8C \uD655\uC778",
1056
+ routes: ["POST projects/[projectId]/domain-claims/[claimId]/verify"],
1057
+ ui: { kind: "built" },
1058
+ // naro domains verify가 소유권 확인을 함께 다시 본다(client.domains.verifyClaim).
1059
+ cli: { kind: "built" },
1060
+ // verify_domain이 소유권 확인을 함께 다시 본다(client.domains.verifyClaim).
1061
+ mcp: { kind: "built" }
1062
+ },
1063
+ {
1064
+ id: "domain-claim-cancel",
1065
+ domain: "domains",
1066
+ title: "\uB3C4\uBA54\uC778 \uC18C\uC720\uAD8C \uD655\uC778 \uCDE8\uC18C",
1067
+ routes: ["DELETE projects/[projectId]/domain-claims/[claimId]"],
1068
+ ui: { kind: "built" },
1069
+ // naro domains remove가 붙어 있지 않은 호스트의 소유권 확인을 취소한다.
1070
+ cli: { kind: "built" },
1071
+ // remove_domain이 붙어 있지 않은 호스트의 소유권 확인을 취소한다(claimCancelled).
1072
+ mcp: { kind: "built" }
1073
+ },
1074
+ {
1075
+ id: "dns-zone-list",
1076
+ domain: "dns",
1077
+ title: "DNS \uC874 \uBAA9\uB85D",
1078
+ routes: ["GET teams/[teamSlug]/dns-zones"],
1079
+ ui: { kind: "built" },
1080
+ cli: { kind: "built", commands: ["naro dns"] },
1081
+ mcp: { kind: "built", tools: ["get_dns"] }
1082
+ },
1083
+ {
1084
+ id: "dns-zone-add",
1085
+ domain: "dns",
1086
+ title: "DNS \uC874 \uCD94\uAC00",
1087
+ routes: ["POST teams/[teamSlug]/dns-zones"],
1088
+ ui: { kind: "built" },
1089
+ cli: { kind: "built", commands: ["naro dns add"] },
1090
+ mcp: { kind: "built", tools: ["add_dns_zone"] }
1091
+ },
1092
+ {
1093
+ id: "dns-zone-remove",
1094
+ domain: "dns",
1095
+ title: "DNS \uC874 \uC0AD\uC81C",
1096
+ routes: ["DELETE teams/[teamSlug]/dns-zones/[zoneId]"],
1097
+ ui: { kind: "built" },
1098
+ cli: { kind: "built", commands: ["naro dns remove"] },
1099
+ mcp: { kind: "built", tools: ["remove_dns_zone"] }
1100
+ },
1101
+ {
1102
+ id: "dns-zone-check",
1103
+ domain: "dns",
1104
+ title: "\uB124\uC784\uC11C\uBC84 \uC774\uAD00 \uD655\uC778",
1105
+ routes: ["POST teams/[teamSlug]/dns-zones/[zoneId]/check"],
1106
+ ui: { kind: "built" },
1107
+ cli: { kind: "built", commands: ["naro dns check"] },
1108
+ mcp: { kind: "built", tools: ["check_dns_zone"] }
1109
+ },
1110
+ {
1111
+ id: "dns-zone-precheck",
1112
+ domain: "dns",
1113
+ title: "\uC774\uAD00 \uC804 \uBA54\uC77C \uD655\uC778",
1114
+ routes: ["GET teams/[teamSlug]/dns-zones/[zoneId]/precheck"],
1115
+ ui: { kind: "built" },
1116
+ cli: { kind: "built", commands: ["naro dns mail"] },
1117
+ mcp: { kind: "built", tools: ["check_dns_mail"] }
1118
+ },
1119
+ {
1120
+ id: "dns-zone-scan",
1121
+ domain: "dns",
1122
+ title: "\uAE30\uC874 DNS \uB808\uCF54\uB4DC \uC2A4\uCE94 \uACB0\uACFC",
1123
+ routes: ["GET teams/[teamSlug]/dns-zones/[zoneId]/scan"],
1124
+ ui: { kind: "built" },
1125
+ cli: { kind: "built", commands: ["naro dns scan"] },
1126
+ mcp: { kind: "built", tools: ["get_dns_scan"] }
1127
+ },
1128
+ {
1129
+ id: "dns-zone-scan-import",
1130
+ domain: "dns",
1131
+ title: "\uC2A4\uCE94\uD55C \uB808\uCF54\uB4DC \uC77C\uAD04 \uAC00\uC838\uC624\uAE30",
1132
+ routes: ["POST teams/[teamSlug]/dns-zones/[zoneId]/scan"],
1133
+ ui: { kind: "built" },
1134
+ cli: { kind: "built", commands: ["naro dns import"] },
1135
+ mcp: { kind: "built", tools: ["import_dns_scan"] }
1136
+ },
1137
+ {
1138
+ id: "dns-record-list",
1139
+ domain: "dns",
1140
+ title: "DNS \uB808\uCF54\uB4DC \uBAA9\uB85D",
1141
+ routes: ["GET teams/[teamSlug]/dns-zones/[zoneId]/records"],
1142
+ ui: { kind: "built" },
1143
+ cli: { kind: "built", commands: ["naro dns records"] },
1144
+ mcp: { kind: "built", tools: ["list_dns_records"] }
1145
+ },
1146
+ {
1147
+ id: "dns-record-add",
1148
+ domain: "dns",
1149
+ title: "DNS \uB808\uCF54\uB4DC \uCD94\uAC00",
1150
+ routes: ["POST teams/[teamSlug]/dns-zones/[zoneId]/records"],
1151
+ ui: { kind: "built" },
1152
+ cli: { kind: "built", commands: ["naro dns records add"] },
1153
+ mcp: { kind: "built", tools: ["add_dns_record"] }
1154
+ },
1155
+ {
1156
+ id: "dns-record-update",
1157
+ domain: "dns",
1158
+ title: "DNS \uB808\uCF54\uB4DC \uC218\uC815",
1159
+ routes: ["PATCH teams/[teamSlug]/dns-zones/[zoneId]/records/[recordId]"],
1160
+ ui: { kind: "built" },
1161
+ cli: { kind: "built", commands: ["naro dns records update"] },
1162
+ mcp: { kind: "built", tools: ["update_dns_record"] }
1163
+ },
1164
+ {
1165
+ id: "dns-record-remove",
1166
+ domain: "dns",
1167
+ title: "DNS \uB808\uCF54\uB4DC \uC0AD\uC81C",
1168
+ routes: ["DELETE teams/[teamSlug]/dns-zones/[zoneId]/records/[recordId]"],
1169
+ ui: { kind: "built" },
1170
+ cli: { kind: "built", commands: ["naro dns records remove"] },
1171
+ mcp: { kind: "built", tools: ["remove_dns_record"] }
1172
+ },
1173
+ {
1174
+ id: "protection-get",
1175
+ domain: "protection",
1176
+ title: "\uD504\uB9AC\uBDF0 \uBCF4\uD638 \uC0C1\uD0DC",
1177
+ routes: ["GET projects/[projectId]/preview-protection"],
1178
+ ui: { kind: "built" },
1179
+ cli: { kind: "built", commands: ["naro protection"] },
1180
+ mcp: { kind: "built", tools: ["get_preview_protection"] }
1181
+ },
1182
+ {
1183
+ id: "protection-set",
1184
+ domain: "protection",
1185
+ title: "\uD504\uB9AC\uBDF0 \uBCF4\uD638 \uCF1C\uAE30\xB7\uB044\uAE30",
1186
+ routes: ["PUT projects/[projectId]/preview-protection"],
1187
+ ui: { kind: "built" },
1188
+ cli: {
1189
+ kind: "built",
1190
+ commands: ["naro protection enable", "naro protection disable"]
1191
+ },
1192
+ mcp: { kind: "built", tools: ["set_preview_protection"] }
1193
+ },
1194
+ {
1195
+ id: "protection-bypass-create",
1196
+ domain: "protection",
1197
+ title: "\uC6B0\uD68C \uC2DC\uD06C\uB9BF \uB9CC\uB4E4\uAE30",
1198
+ routes: ["POST projects/[projectId]/preview-protection/bypass"],
1199
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1200
+ ui: { kind: "built" },
1201
+ cli: { kind: "built", commands: ["naro protection bypass"] },
1202
+ mcp: {
1203
+ kind: "excluded",
1204
+ reason: "secret-value",
1205
+ note: "apps/mcp/src/protection-tools.ts \u2014 \uC6B0\uD68C \uC2DC\uD06C\uB9BF\uC740 \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. \uB300\uC2DC\uBCF4\uB4DC\uB098 naro protection bypass\uB85C \uB9CC\uB4E0\uB2E4."
1206
+ }
1207
+ },
1208
+ {
1209
+ id: "protection-bypass-remove",
1210
+ domain: "protection",
1211
+ title: "\uC6B0\uD68C \uC2DC\uD06C\uB9BF \uC81C\uAC70",
1212
+ routes: ["DELETE projects/[projectId]/preview-protection/bypass"],
1213
+ ui: { kind: "built" },
1214
+ cli: { kind: "built", commands: ["naro protection bypass remove"] },
1215
+ mcp: { kind: "built", tools: ["remove_preview_protection_bypass"] }
1216
+ },
1217
+ {
1218
+ id: "database-info",
1219
+ domain: "database",
1220
+ title: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC0C1\uD0DC\xB7\uC6A9\uB7C9\xB7\uD55C\uB3C4",
1221
+ routes: ["GET projects/[projectId]/database"],
1222
+ ui: { kind: "built" },
1223
+ cli: { kind: "built", commands: ["naro db info"] },
1224
+ mcp: { kind: "built", tools: ["get_database"] }
1225
+ },
1226
+ {
1227
+ id: "database-enable",
1228
+ domain: "database",
1229
+ title: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uB9CC\uB4E4\uAE30",
1230
+ routes: ["POST projects/[projectId]/database"],
1231
+ ui: { kind: "built" },
1232
+ cli: { kind: "built", commands: ["naro db enable"] },
1233
+ mcp: { kind: "built", tools: ["enable_database"] }
1234
+ },
1235
+ {
1236
+ id: "database-query",
1237
+ domain: "database",
1238
+ title: "SQL \uC2E4\uD589",
1239
+ routes: ["POST projects/[projectId]/database/query"],
1240
+ ui: { kind: "built" },
1241
+ cli: { kind: "built", commands: ["naro db query"] },
1242
+ mcp: { kind: "built", tools: ["execute_sql"] }
1243
+ },
1244
+ {
1245
+ id: "database-tables",
1246
+ domain: "database",
1247
+ title: "\uD14C\uC774\uBE14 \uBAA9\uB85D",
1248
+ routes: ["GET projects/[projectId]/database/tables"],
1249
+ ui: { kind: "built" },
1250
+ // naro db 명령들이 테이블 목록을 읽는다(client.db.tables).
1251
+ cli: { kind: "built" },
1252
+ mcp: { kind: "built", tools: ["list_tables"] }
1253
+ },
1254
+ {
1255
+ id: "database-schema-change",
1256
+ domain: "database",
1257
+ title: "\uD14C\uC774\uBE14\xB7\uC778\uB371\uC2A4 \uBC14\uAFB8\uAE30",
1258
+ routes: ["POST projects/[projectId]/database/schema"],
1259
+ ui: { kind: "built" },
1260
+ cli: {
1261
+ kind: "built",
1262
+ commands: [
1263
+ "naro db table create",
1264
+ "naro db table add-column",
1265
+ "naro db table rename-column",
1266
+ "naro db table rename",
1267
+ "naro db table drop-column",
1268
+ "naro db table drop",
1269
+ "naro db index create",
1270
+ "naro db index drop"
1271
+ ]
1272
+ },
1273
+ mcp: {
1274
+ kind: "built",
1275
+ tools: [
1276
+ "create_table",
1277
+ "alter_table",
1278
+ "drop_table",
1279
+ "create_index",
1280
+ "drop_index"
1281
+ ]
1282
+ }
1283
+ },
1284
+ {
1285
+ id: "database-migration-list",
1286
+ domain: "database",
1287
+ title: "\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uBAA9\uB85D",
1288
+ routes: ["GET projects/[projectId]/database/migrations"],
1289
+ ui: { kind: "built" },
1290
+ cli: { kind: "built", commands: ["naro migration list"] },
1291
+ mcp: { kind: "built", tools: ["list_migrations"] }
1292
+ },
1293
+ {
1294
+ id: "database-migration-apply",
1295
+ domain: "database",
1296
+ title: "\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC801\uC6A9",
1297
+ routes: ["POST projects/[projectId]/database/migrations"],
1298
+ ui: { kind: "built" },
1299
+ cli: { kind: "built", commands: ["naro db push", "naro migration up"] },
1300
+ mcp: { kind: "built", tools: ["apply_migration"] }
1301
+ },
1302
+ {
1303
+ id: "database-export",
1304
+ domain: "database",
1305
+ title: "\uB364\uD504 \uB0B4\uB824\uBC1B\uAE30",
1306
+ routes: ["POST projects/[projectId]/database/export"],
1307
+ ui: { kind: "built" },
1308
+ cli: { kind: "built", commands: ["naro db dump"] },
1309
+ mcp: { kind: "built", tools: ["export_database"] }
1310
+ },
1311
+ {
1312
+ id: "database-restore",
1313
+ domain: "database",
1314
+ title: "Time Travel \uBCF5\uAD6C",
1315
+ routes: ["POST projects/[projectId]/database/restore"],
1316
+ ui: { kind: "built" },
1317
+ cli: { kind: "built", commands: ["naro db restore"] },
1318
+ mcp: { kind: "built", tools: ["restore_database"] }
1319
+ },
1320
+ {
1321
+ id: "database-bookmark",
1322
+ domain: "database",
1323
+ title: "\uBCF5\uAD6C \uC9C0\uC810(\uBD81\uB9C8\uD06C) \uC870\uD68C",
1324
+ routes: ["GET projects/[projectId]/database/bookmark"],
1325
+ ui: { kind: "built" },
1326
+ // naro db restore가 되돌릴 북마크를 먼저 읽는다(client.db.bookmark).
1327
+ cli: { kind: "built" },
1328
+ // restore_database가 되돌릴 북마크를 먼저 읽는다(client.db.bookmark).
1329
+ mcp: { kind: "built" }
1330
+ },
1331
+ {
1332
+ id: "database-pull",
1333
+ domain: "database",
1334
+ title: "\uC6D0\uACA9 \uC2A4\uD0A4\uB9C8\uB97C \uBCA0\uC774\uC2A4\uB77C\uC778 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uC73C\uB85C",
1335
+ routes: ["POST projects/[projectId]/database/pull"],
1336
+ ui: {
1337
+ kind: "excluded",
1338
+ reason: "local-filesystem",
1339
+ note: "\uC6D0\uACA9 \uC2A4\uD0A4\uB9C8\uB97C ./migrations\uC758 \uCCAB \uD30C\uC77C\uB85C \uC4F4\uB2E4 \u2014 \uB0B4 \uCEF4\uD4E8\uD130\uC758 \uD3F4\uB354\uAC00 \uC788\uC5B4\uC57C \uD558\uB294 \uC77C\uC774\uB77C \uD654\uBA74\uC5D0\uC11C\uB294 \uD560 \uC218 \uC5C6\uB2E4."
1340
+ },
1341
+ cli: { kind: "built", commands: ["naro db pull"] },
1342
+ mcp: { kind: "built", tools: ["pull_database_schema"] }
1343
+ },
1344
+ {
1345
+ id: "database-reset",
1346
+ domain: "database",
1347
+ title: "\uC804\uCCB4 \uB9AC\uC14B \uD6C4 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uB2E4\uC2DC \uC801\uC6A9",
1348
+ ui: {
1349
+ kind: "excluded",
1350
+ reason: "local-filesystem",
1351
+ note: "./migrations\uB97C \uCC98\uC74C\uBD80\uD130 \uB2E4\uC2DC \uC801\uC6A9\uD55C\uB2E4 \u2014 \uB0B4 \uCEF4\uD4E8\uD130\uC758 \uD3F4\uB354\uAC00 \uC788\uC5B4\uC57C \uD558\uB294 \uC77C\uC774\uB77C \uD654\uBA74\uC5D0\uC11C\uB294 \uD560 \uC218 \uC5C6\uB2E4."
1352
+ },
1353
+ cli: { kind: "built", commands: ["naro db reset"] },
1354
+ mcp: {
1355
+ kind: "excluded",
1356
+ reason: "irreversible",
1357
+ note: "\uBAA8\uB4E0 \uD14C\uC774\uBE14\uACFC \uC815\uCC45\uC744 \uC9C0\uC6B0\uACE0 \uB2E4\uC2DC \uB9CC\uB4E0\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1358
+ }
1359
+ },
1360
+ {
1361
+ id: "database-lint",
1362
+ domain: "database",
1363
+ title: "\uC2A4\uD0A4\uB9C8\xB7RLS \uC815\uCC45 \uB9B0\uD2B8",
1364
+ ui: { kind: "built" },
1365
+ cli: { kind: "built", commands: ["naro db lint"] },
1366
+ mcp: { kind: "built", tools: ["lint_database"] }
1367
+ },
1368
+ {
1369
+ id: "database-gen-types",
1370
+ domain: "database",
1371
+ title: "\uC2A4\uD0A4\uB9C8\uC5D0\uC11C TypeScript \uD0C0\uC785 \uC0DD\uC131",
1372
+ ui: { kind: "built" },
1373
+ cli: { kind: "built", commands: ["naro gen types"] },
1374
+ mcp: { kind: "built", tools: ["generate_typescript_types"] }
1375
+ },
1376
+ {
1377
+ id: "database-migration-new",
1378
+ domain: "database",
1379
+ title: "\uC0C8 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uD30C\uC77C",
1380
+ ui: {
1381
+ kind: "excluded",
1382
+ reason: "local-filesystem",
1383
+ note: "./migrations\uC5D0 \uD30C\uC77C\uC744 \uB9CC\uB4E0\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
1384
+ },
1385
+ cli: { kind: "built", commands: ["naro migration new"] },
1386
+ mcp: {
1387
+ kind: "excluded",
1388
+ reason: "agent-native",
1389
+ note: "./migrations\uC5D0 \uD30C\uC77C \uD558\uB098\uB97C \uB9CC\uB4E0\uB2E4. apply_migration\uC740 \uC801\uC6A9\uD558\uBA74\uC11C \uD30C\uC77C\uC744 \uC4F4\uB2E4. 2026-11-03 \uACB0\uC815."
1390
+ }
1391
+ },
1392
+ {
1393
+ id: "database-migration-repair",
1394
+ domain: "database",
1395
+ title: "\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC6D0\uC7A5 \uC218\uB9AC",
1396
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/migrations",
1397
+ ui: { kind: "built" },
1398
+ cli: { kind: "built", commands: ["naro migration repair"] },
1399
+ mcp: {
1400
+ kind: "excluded",
1401
+ reason: "irreversible",
1402
+ note: "\uC6D0\uC7A5\uB9CC \uACE0\uCCD0 \uC801\uC6A9 \uAE30\uB85D\uACFC \uC2E4\uC81C \uC2A4\uD0A4\uB9C8\uAC00 \uC5B4\uAE0B\uB0A0 \uC218 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1403
+ }
1404
+ },
1405
+ {
1406
+ id: "database-saved-query-list",
1407
+ domain: "database",
1408
+ title: "\uC800\uC7A5\uD55C \uCFFC\uB9AC \uBAA9\uB85D",
1409
+ routes: ["GET projects/[projectId]/database/saved-queries"],
1410
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1411
+ ui: { kind: "built" },
1412
+ cli: {
1413
+ kind: "excluded",
1414
+ reason: "ui-affordance",
1415
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. CLI\uB294 \uCFFC\uB9AC\uB97C \uD30C\uC77C\uC5D0 \uB450\uACE0 naro db query --file\uB85C \uB3CC\uB9B0\uB2E4."
1416
+ },
1417
+ mcp: {
1418
+ kind: "excluded",
1419
+ reason: "ui-affordance",
1420
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1421
+ }
1422
+ },
1423
+ {
1424
+ id: "database-saved-query-create",
1425
+ domain: "database",
1426
+ title: "\uCFFC\uB9AC \uC800\uC7A5",
1427
+ routes: ["POST projects/[projectId]/database/saved-queries"],
1428
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1429
+ ui: { kind: "built" },
1430
+ cli: {
1431
+ kind: "excluded",
1432
+ reason: "ui-affordance",
1433
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. CLI\uB294 \uCFFC\uB9AC\uB97C \uD30C\uC77C\uC5D0 \uB450\uACE0 naro db query --file\uB85C \uB3CC\uB9B0\uB2E4."
1434
+ },
1435
+ mcp: {
1436
+ kind: "excluded",
1437
+ reason: "ui-affordance",
1438
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1439
+ }
1440
+ },
1441
+ {
1442
+ id: "database-saved-query-update",
1443
+ domain: "database",
1444
+ title: "\uC800\uC7A5\uD55C \uCFFC\uB9AC \uC218\uC815",
1445
+ routes: [
1446
+ "PATCH projects/[projectId]/database/saved-queries/[savedQueryId]"
1447
+ ],
1448
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1449
+ ui: { kind: "built" },
1450
+ cli: {
1451
+ kind: "excluded",
1452
+ reason: "ui-affordance",
1453
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. CLI\uB294 \uCFFC\uB9AC\uB97C \uD30C\uC77C\uC5D0 \uB450\uACE0 naro db query --file\uB85C \uB3CC\uB9B0\uB2E4."
1454
+ },
1455
+ mcp: {
1456
+ kind: "excluded",
1457
+ reason: "ui-affordance",
1458
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1459
+ }
1460
+ },
1461
+ {
1462
+ id: "database-saved-query-delete",
1463
+ domain: "database",
1464
+ title: "\uC800\uC7A5\uD55C \uCFFC\uB9AC \uC0AD\uC81C",
1465
+ routes: [
1466
+ "DELETE projects/[projectId]/database/saved-queries/[savedQueryId]"
1467
+ ],
1468
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1469
+ ui: { kind: "built" },
1470
+ cli: {
1471
+ kind: "excluded",
1472
+ reason: "ui-affordance",
1473
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. CLI\uB294 \uCFFC\uB9AC\uB97C \uD30C\uC77C\uC5D0 \uB450\uACE0 naro db query --file\uB85C \uB3CC\uB9B0\uB2E4."
1474
+ },
1475
+ mcp: {
1476
+ kind: "excluded",
1477
+ reason: "ui-affordance",
1478
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1479
+ }
1480
+ },
1481
+ {
1482
+ // 대시보드 정책 화면(백엔드 메뉴 Policies)은 2026-11-04에 생겼다.
1483
+ id: "rls-table-policy-list",
1484
+ domain: "rls",
1485
+ title: "\uD14C\uC774\uBE14\uBCC4 \uC815\uCC45 \uC870\uD68C",
1486
+ routes: ["GET projects/[projectId]/backend/policies"],
1487
+ ui: { kind: "built" },
1488
+ cli: { kind: "built", commands: ["naro policy rules"] },
1489
+ mcp: { kind: "built", tools: ["list_policy_rules"] }
1490
+ },
1491
+ {
1492
+ id: "rls-table-policy-set",
1493
+ domain: "rls",
1494
+ title: "\uD14C\uC774\uBE14\uC758 \uC561\uC158\uBCC4 \uADDC\uCE59 \uC124\uC815",
1495
+ routes: ["PUT projects/[projectId]/backend/policies/[table]"],
1496
+ ui: { kind: "built" },
1497
+ cli: { kind: "built", commands: ["naro policy set"] },
1498
+ // 규칙은 CREATE POLICY로, owner 규칙은 COMMENT ON POLICY p ON t IS 'naro:owner <column>'로 apply_migration에 쓴다.
1499
+ mcp: { kind: "built" }
1500
+ },
1501
+ {
1502
+ id: "rls-table-policy-remove",
1503
+ domain: "rls",
1504
+ title: "\uD14C\uC774\uBE14\uC758 \uC815\uCC45 \uC804\uBD80 \uC81C\uAC70",
1505
+ routes: ["DELETE projects/[projectId]/backend/policies/[table]"],
1506
+ ui: { kind: "built" },
1507
+ cli: { kind: "built", commands: ["naro policy remove"] },
1508
+ // apply_migration에 그 테이블의 DROP POLICY를 쓴다.
1509
+ mcp: { kind: "built" }
1510
+ },
1511
+ {
1512
+ id: "rls-policy-list",
1513
+ domain: "rls",
1514
+ title: "\uC815\uCC45 \uBAA9\uB85D(CREATE POLICY \uAF34)",
1515
+ routes: ["GET projects/[projectId]/backend/rls/policies"],
1516
+ ui: { kind: "built" },
1517
+ cli: { kind: "built", commands: ["naro policy list"] },
1518
+ mcp: { kind: "built", tools: ["list_policies"] }
1519
+ },
1520
+ {
1521
+ id: "rls-policy-create",
1522
+ domain: "rls",
1523
+ title: "\uC815\uCC45 \uB9CC\uB4E4\uAE30(CREATE POLICY)",
1524
+ routes: ["POST projects/[projectId]/backend/rls/policies"],
1525
+ ui: { kind: "built" },
1526
+ cli: { kind: "built", commands: ["naro policy create"] },
1527
+ // apply_migration·execute_sql이 CREATE POLICY를 받는다. 테이블과 같은 마이그레이션에 쓰라고 apply_migration이 안내한다.
1528
+ mcp: { kind: "built" }
1529
+ },
1530
+ {
1531
+ id: "rls-policy-replace",
1532
+ domain: "rls",
1533
+ title: "\uC815\uCC45 \uBC14\uAFB8\uAE30",
1534
+ routes: ["PUT projects/[projectId]/backend/rls/policies/[table]/[name]"],
1535
+ ui: { kind: "built" },
1536
+ // naro migration up·db push(client.db.applyMigration)나 naro db query
1537
+ // (client.db.query) 한 번에 DROP POLICY와 CREATE POLICY를 함께 쓴다.
1538
+ // MCP와 같은 경로다(PR #414 리뷰).
1539
+ cli: { kind: "built" },
1540
+ // apply_migration 한 번에 DROP POLICY와 CREATE POLICY를 함께 쓴다.
1541
+ mcp: { kind: "built" }
1542
+ },
1543
+ {
1544
+ id: "rls-policy-drop",
1545
+ domain: "rls",
1546
+ title: "\uC815\uCC45 \uC0AD\uC81C(DROP POLICY)",
1547
+ routes: ["DELETE projects/[projectId]/backend/rls/policies/[table]/[name]"],
1548
+ ui: { kind: "built" },
1549
+ cli: { kind: "built", commands: ["naro policy drop"] },
1550
+ // apply_migration·execute_sql이 DROP POLICY를 받는다.
1551
+ mcp: { kind: "built" }
1552
+ },
1553
+ {
1554
+ id: "storage-info",
1555
+ domain: "storage",
1556
+ title: "Storage \uC0C1\uD0DC",
1557
+ routes: ["GET projects/[projectId]/backend/storage"],
1558
+ ui: { kind: "built" },
1559
+ cli: { kind: "built", commands: ["naro storage"] },
1560
+ mcp: { kind: "built", tools: ["get_storage"] }
1561
+ },
1562
+ {
1563
+ id: "storage-enable",
1564
+ domain: "storage",
1565
+ title: "Storage \uCF1C\uAE30",
1566
+ routes: ["POST projects/[projectId]/backend/storage"],
1567
+ ui: { kind: "built" },
1568
+ cli: { kind: "built", commands: ["naro storage enable"] },
1569
+ mcp: { kind: "built", tools: ["enable_storage"] }
1570
+ },
1571
+ {
1572
+ id: "storage-disable",
1573
+ domain: "storage",
1574
+ title: "Storage \uB044\uAE30",
1575
+ routes: ["DELETE projects/[projectId]/backend/storage"],
1576
+ ui: { kind: "built" },
1577
+ cli: { kind: "built", commands: ["naro storage disable"] },
1578
+ mcp: { kind: "built", tools: ["disable_storage"] }
1579
+ },
1580
+ {
1581
+ id: "storage-bucket-list",
1582
+ domain: "storage",
1583
+ title: "\uBC84\uD0B7 \uBAA9\uB85D",
1584
+ routes: ["GET projects/[projectId]/backend/storage/buckets"],
1585
+ ui: { kind: "built" },
1586
+ cli: { kind: "built", commands: ["naro storage bucket list"] },
1587
+ mcp: { kind: "built", tools: ["list_storage_buckets"] }
1588
+ },
1589
+ {
1590
+ id: "storage-bucket-create",
1591
+ domain: "storage",
1592
+ title: "\uBC84\uD0B7 \uB9CC\uB4E4\uAE30",
1593
+ routes: ["POST projects/[projectId]/backend/storage/buckets"],
1594
+ ui: { kind: "built" },
1595
+ cli: { kind: "built", commands: ["naro storage bucket create"] },
1596
+ mcp: { kind: "built", tools: ["create_storage_bucket"] }
1597
+ },
1598
+ {
1599
+ id: "storage-bucket-get",
1600
+ domain: "storage",
1601
+ title: "\uBC84\uD0B7 \uC870\uD68C",
1602
+ routes: ["GET projects/[projectId]/backend/storage/buckets/[bucket]"],
1603
+ ui: { kind: "built" },
1604
+ // naro storage bucket update가 바꾸기 전에 읽는다(client.storage.getBucket).
1605
+ cli: { kind: "built" },
1606
+ mcp: { kind: "built", tools: ["get_storage_bucket"] }
1607
+ },
1608
+ {
1609
+ id: "storage-bucket-update",
1610
+ domain: "storage",
1611
+ title: "\uBC84\uD0B7 \uC124\uC815 \uBCC0\uACBD",
1612
+ routes: ["PATCH projects/[projectId]/backend/storage/buckets/[bucket]"],
1613
+ ui: { kind: "built" },
1614
+ cli: { kind: "built", commands: ["naro storage bucket update"] },
1615
+ mcp: { kind: "built", tools: ["update_storage_bucket"] }
1616
+ },
1617
+ {
1618
+ id: "storage-bucket-remove",
1619
+ domain: "storage",
1620
+ title: "\uBC84\uD0B7 \uC0AD\uC81C",
1621
+ routes: ["DELETE projects/[projectId]/backend/storage/buckets/[bucket]"],
1622
+ ui: { kind: "built" },
1623
+ cli: { kind: "built", commands: ["naro storage bucket remove"] },
1624
+ mcp: { kind: "built", tools: ["delete_storage_bucket"] }
1625
+ },
1626
+ {
1627
+ id: "storage-bucket-empty",
1628
+ domain: "storage",
1629
+ title: "\uBC84\uD0B7 \uBE44\uC6B0\uAE30",
1630
+ routes: [
1631
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/empty"
1632
+ ],
1633
+ ui: { kind: "built" },
1634
+ cli: { kind: "built", commands: ["naro storage bucket empty"] },
1635
+ mcp: { kind: "built", tools: ["empty_storage_bucket"] }
1636
+ },
1637
+ {
1638
+ id: "storage-object-list",
1639
+ domain: "storage",
1640
+ title: "\uAC1D\uCCB4 \uBAA9\uB85D",
1641
+ routes: [
1642
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/list"
1643
+ ],
1644
+ ui: { kind: "built" },
1645
+ cli: { kind: "built", commands: ["naro storage ls"] },
1646
+ mcp: { kind: "built", tools: ["list_storage_objects"] }
1647
+ },
1648
+ {
1649
+ id: "storage-object-remove",
1650
+ domain: "storage",
1651
+ title: "\uAC1D\uCCB4 \uC0AD\uC81C",
1652
+ routes: [
1653
+ "DELETE projects/[projectId]/backend/storage/buckets/[bucket]/objects"
1654
+ ],
1655
+ ui: { kind: "built" },
1656
+ cli: { kind: "built", commands: ["naro storage rm"] },
1657
+ mcp: { kind: "built", tools: ["delete_storage_objects"] }
1658
+ },
1659
+ {
1660
+ id: "storage-object-sign",
1661
+ domain: "storage",
1662
+ title: "\uB2E4\uC6B4\uB85C\uB4DC \uC11C\uBA85 URL",
1663
+ routes: [
1664
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/sign"
1665
+ ],
1666
+ ui: { kind: "built" },
1667
+ cli: { kind: "built", commands: ["naro storage sign"] },
1668
+ mcp: { kind: "built", tools: ["create_storage_signed_url"] }
1669
+ },
1670
+ {
1671
+ id: "storage-object-upload",
1672
+ domain: "storage",
1673
+ title: "\uC5C5\uB85C\uB4DC URL \uBC1C\uAE09",
1674
+ routes: [
1675
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/upload-url"
1676
+ ],
1677
+ ui: { kind: "built" },
1678
+ cli: { kind: "built", commands: ["naro storage cp"] },
1679
+ mcp: { kind: "built", tools: ["create_storage_upload_url"] }
1680
+ },
1681
+ {
1682
+ id: "storage-object-move",
1683
+ domain: "storage",
1684
+ title: "\uAC1D\uCCB4 \uC774\uB3D9",
1685
+ routes: [
1686
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/move"
1687
+ ],
1688
+ ui: { kind: "built" },
1689
+ cli: { kind: "built", commands: ["naro storage mv"] },
1690
+ mcp: { kind: "built", tools: ["move_storage_object"] }
1691
+ },
1692
+ {
1693
+ id: "realtime-info",
1694
+ domain: "realtime",
1695
+ title: "Realtime \uC0C1\uD0DC\xB7\uC5F0\uACB0 \uC218",
1696
+ routes: ["GET projects/[projectId]/backend/realtime"],
1697
+ ui: { kind: "built" },
1698
+ cli: { kind: "built", commands: ["naro realtime"] },
1699
+ mcp: { kind: "built", tools: ["get_realtime"] }
1700
+ },
1701
+ {
1702
+ id: "realtime-enable",
1703
+ domain: "realtime",
1704
+ title: "Realtime \uCF1C\uAE30",
1705
+ routes: ["POST projects/[projectId]/backend/realtime"],
1706
+ ui: { kind: "built" },
1707
+ cli: { kind: "built", commands: ["naro realtime enable"] },
1708
+ mcp: { kind: "built", tools: ["enable_realtime"] }
1709
+ },
1710
+ {
1711
+ id: "realtime-table-list",
1712
+ domain: "realtime",
1713
+ title: "\uACF5\uAC1C \uD14C\uC774\uBE14 \uBAA9\uB85D",
1714
+ routes: ["GET projects/[projectId]/backend/realtime/tables"],
1715
+ ui: { kind: "built" },
1716
+ cli: { kind: "built", commands: ["naro realtime tables"] },
1717
+ // get_realtime이 공개 테이블을 상태와 함께 돌려준다.
1718
+ mcp: { kind: "built" }
1719
+ },
1720
+ {
1721
+ id: "realtime-table-add",
1722
+ domain: "realtime",
1723
+ title: "\uD14C\uC774\uBE14 \uBCC0\uACBD \uACF5\uAC1C",
1724
+ routes: ["PUT projects/[projectId]/backend/realtime/tables/[table]"],
1725
+ ui: { kind: "built" },
1726
+ cli: { kind: "built", commands: ["naro realtime tables add"] },
1727
+ mcp: { kind: "built", tools: ["publish_realtime_table"] }
1728
+ },
1729
+ {
1730
+ id: "realtime-table-remove",
1731
+ domain: "realtime",
1732
+ title: "\uD14C\uC774\uBE14 \uBCC0\uACBD \uACF5\uAC1C \uC911\uC9C0",
1733
+ routes: ["DELETE projects/[projectId]/backend/realtime/tables/[table]"],
1734
+ ui: { kind: "built" },
1735
+ cli: { kind: "built", commands: ["naro realtime tables remove"] },
1736
+ mcp: { kind: "built", tools: ["unpublish_realtime_table"] }
1737
+ },
1738
+ {
1739
+ // 대시보드 Functions 화면은 2026-11-04에 생겼다. 그 전의 built는 틀렸다.
1740
+ id: "function-list",
1741
+ domain: "functions",
1742
+ title: "\uD568\uC218 \uBAA9\uB85D",
1743
+ routes: ["GET projects/[projectId]/backend/functions"],
1744
+ ui: { kind: "built" },
1745
+ cli: { kind: "built", commands: ["naro functions"] },
1746
+ mcp: { kind: "built", tools: ["list_functions"] }
1747
+ },
1748
+ {
1749
+ id: "function-scaffold",
1750
+ domain: "functions",
1751
+ title: "\uD568\uC218 \uBF08\uB300 \uB9CC\uB4E4\uAE30",
1752
+ ui: {
1753
+ kind: "excluded",
1754
+ reason: "local-filesystem",
1755
+ note: "naro/functions/<name>/index.ts\uB97C \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
1756
+ },
1757
+ cli: { kind: "built", commands: ["naro functions new"] },
1758
+ mcp: {
1759
+ kind: "excluded",
1760
+ reason: "agent-native",
1761
+ note: "naro/functions/<name>/index.ts\uB97C \uC4F4\uB2E4. 2026-11-03 \uACB0\uC815."
1762
+ }
1763
+ },
1764
+ {
1765
+ id: "function-get",
1766
+ domain: "functions",
1767
+ title: "\uD568\uC218 \uC0C1\uC138",
1768
+ routes: ["GET projects/[projectId]/backend/functions/[name]"],
1769
+ ui: { kind: "built" },
1770
+ cli: { kind: "built", commands: ["naro functions versions"] },
1771
+ mcp: { kind: "built", tools: ["get_function"] }
1772
+ },
1773
+ {
1774
+ id: "function-update",
1775
+ domain: "functions",
1776
+ title: "\uD568\uC218 verify_jwt \uC124\uC815",
1777
+ routes: ["PATCH projects/[projectId]/backend/functions/[name]"],
1778
+ ui: { kind: "built" },
1779
+ cli: { kind: "built", commands: ["naro functions update"] },
1780
+ mcp: { kind: "built", tools: ["update_function"] }
1781
+ },
1782
+ {
1783
+ id: "function-delete",
1784
+ domain: "functions",
1785
+ title: "\uD568\uC218 \uC0AD\uC81C",
1786
+ routes: ["DELETE projects/[projectId]/backend/functions/[name]"],
1787
+ ui: { kind: "built" },
1788
+ cli: { kind: "built", commands: ["naro functions delete"] },
1789
+ mcp: { kind: "built", tools: ["delete_function"] }
1790
+ },
1791
+ {
1792
+ id: "function-version-upload",
1793
+ domain: "functions",
1794
+ title: "\uBC88\uB4E4\uD55C \uCF54\uB4DC\uB97C \uC0C8 \uBC84\uC804\uC73C\uB85C \uC62C\uB9AC\uAE30",
1795
+ routes: ["POST projects/[projectId]/backend/functions/[name]/versions"],
1796
+ ui: {
1797
+ kind: "excluded",
1798
+ reason: "local-filesystem",
1799
+ note: "\uB85C\uCEEC\uC5D0\uC11C \uBC88\uB4E4\uD558\uACE0 workerd\uB85C \uBD80\uD305 \uD655\uC778\uD55C \uCF54\uB4DC\uB97C \uC62C\uB9B0\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uC5D0\uB294 \uBC88\uB4E4\uD560 \uC18C\uC2A4\uAC00 \uC5C6\uB2E4."
1800
+ },
1801
+ cli: { kind: "built", commands: ["naro functions deploy"] },
1802
+ mcp: { kind: "built", tools: ["deploy_function"] }
1803
+ },
1804
+ {
1805
+ id: "function-version-deploy",
1806
+ domain: "functions",
1807
+ title: "\uC800\uC7A5\uB41C \uBC84\uC804 \uBC30\uD3EC",
1808
+ routes: [
1809
+ "POST projects/[projectId]/backend/functions/[name]/versions/[versionId]/deploy"
1810
+ ],
1811
+ ui: { kind: "built" },
1812
+ // naro functions deploy가 올린 버전을 배포한다(client.functions.deploy).
1813
+ cli: { kind: "built" },
1814
+ // deploy_function이 naro-cli의 deployFunctions로 올린 버전을 배포한다.
1815
+ mcp: { kind: "built" }
1816
+ },
1817
+ {
1818
+ id: "function-rollback",
1819
+ domain: "functions",
1820
+ title: "\uC774\uC804 \uBC84\uC804\uC73C\uB85C \uB864\uBC31",
1821
+ routes: ["POST projects/[projectId]/backend/functions/[name]/rollback"],
1822
+ ui: { kind: "built" },
1823
+ cli: { kind: "built", commands: ["naro functions rollback"] },
1824
+ mcp: { kind: "built", tools: ["rollback_function"] }
1825
+ },
1826
+ {
1827
+ id: "function-logs",
1828
+ domain: "functions",
1829
+ title: "\uD568\uC218 \uB85C\uADF8",
1830
+ routes: ["GET projects/[projectId]/backend/functions/[name]/logs"],
1831
+ ui: { kind: "built" },
1832
+ cli: { kind: "built", commands: ["naro functions logs"] },
1833
+ mcp: { kind: "built", tools: ["get_function_logs"] }
1834
+ },
1835
+ {
1836
+ id: "function-invoke",
1837
+ domain: "functions",
1838
+ title: "\uD568\uC218 \uD638\uCD9C",
1839
+ routes: ["POST projects/[projectId]/backend/functions/[name]/invoke"],
1840
+ ui: { kind: "built" },
1841
+ cli: { kind: "built", commands: ["naro functions invoke"] },
1842
+ mcp: { kind: "built", tools: ["invoke_function"] }
1843
+ },
1844
+ {
1845
+ id: "function-secret-list",
1846
+ domain: "functions",
1847
+ title: "\uD568\uC218 \uC2DC\uD06C\uB9BF \uBAA9\uB85D",
1848
+ routes: ["GET projects/[projectId]/backend/functions-secrets"],
1849
+ ui: { kind: "built" },
1850
+ cli: { kind: "built", commands: ["naro functions secrets"] },
1851
+ mcp: { kind: "built", tools: ["list_function_secrets"] }
1852
+ },
1853
+ {
1854
+ id: "function-secret-set",
1855
+ domain: "functions",
1856
+ title: "\uD568\uC218 \uC2DC\uD06C\uB9BF \uC124\uC815",
1857
+ routes: ["PUT projects/[projectId]/backend/functions-secrets"],
1858
+ dashboard: "/[teamSlug]/projects/[projectSlug]/functions/secrets",
1859
+ ui: { kind: "built" },
1860
+ cli: {
1861
+ kind: "built",
1862
+ commands: ["naro functions secrets set", "naro functions secrets import"]
1863
+ },
1864
+ mcp: {
1865
+ kind: "excluded",
1866
+ reason: "secret-value",
1867
+ note: "apps/mcp/src/function-tools.ts\uC758 NO_SECRET_VALUES \u2014 \uAC12\uC774 \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. \uC0AC\uC6A9\uC790\uAC00 naro functions secrets set\uC744 \uB3CC\uB9AC\uAC8C \uD55C\uB2E4."
1868
+ }
1869
+ },
1870
+ // 라우트는 function-secret-set과 같은 PUT functions-secrets다. 한 라우트는
1871
+ // 한 기능에만 걸리므로 여기에는 적지 않는다.
1872
+ {
1873
+ id: "function-secret-unset",
1874
+ domain: "functions",
1875
+ title: "\uD568\uC218 \uC2DC\uD06C\uB9BF \uD574\uC81C",
1876
+ ui: { kind: "built" },
1877
+ cli: { kind: "built", commands: ["naro functions secrets unset"] },
1878
+ mcp: { kind: "built", tools: ["unset_function_secret"] }
1879
+ },
1880
+ {
1881
+ id: "function-sync",
1882
+ domain: "functions",
1883
+ title: "\uD568\uC218 \uC7AC\uB3D9\uAE30\uD654",
1884
+ routes: ["POST projects/[projectId]/backend/functions-sync"],
1885
+ ui: { kind: "built" },
1886
+ cli: { kind: "built", commands: ["naro functions secrets sync"] },
1887
+ mcp: { kind: "built", tools: ["sync_functions"] }
1888
+ },
1889
+ {
1890
+ id: "backend-info",
1891
+ domain: "backend",
1892
+ title: "Backend \uC0C1\uD0DC\xB7URL\xB7anon \uD0A4",
1893
+ routes: ["GET projects/[projectId]/backend"],
1894
+ ui: { kind: "built" },
1895
+ cli: { kind: "built", commands: ["naro backend"] },
1896
+ mcp: { kind: "built", tools: ["get_backend"] }
1897
+ },
1898
+ {
1899
+ id: "backend-enable",
1900
+ domain: "backend",
1901
+ title: "Backend \uCF1C\uAE30",
1902
+ routes: ["POST projects/[projectId]/backend/enable"],
1903
+ ui: { kind: "built" },
1904
+ cli: { kind: "built", commands: ["naro backend enable"] },
1905
+ mcp: { kind: "built", tools: ["enable_backend"] }
1906
+ },
1907
+ {
1908
+ id: "backend-provision",
1909
+ domain: "backend",
1910
+ title: "Backend \uD504\uB85C\uBE44\uC800\uB2DD \uC774\uC5B4\uAC00\uAE30",
1911
+ routes: ["POST projects/[projectId]/backend/provision"],
1912
+ ui: { kind: "built" },
1913
+ // naro backend enable이 멈춘 프로비저닝을 이어 간다.
1914
+ cli: { kind: "built" },
1915
+ // enable_backend가 같은 호출이다(client.backend.enable — 라우트 provision은 그 옛 이름).
1916
+ mcp: { kind: "built" }
1917
+ },
1918
+ {
1919
+ id: "backend-disable",
1920
+ domain: "backend",
1921
+ title: "Backend \uB044\uAE30",
1922
+ routes: ["POST projects/[projectId]/backend/disable"],
1923
+ ui: { kind: "built" },
1924
+ cli: { kind: "built", commands: ["naro backend disable"] },
1925
+ mcp: { kind: "built", tools: ["disable_backend"] }
1926
+ },
1927
+ {
1928
+ id: "backend-keys",
1929
+ domain: "backend",
1930
+ title: "API \uD0A4 \uC870\uD68C(service \uD0A4 \uD3EC\uD568)",
1931
+ routes: ["GET projects/[projectId]/backend/keys"],
1932
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1933
+ ui: { kind: "built" },
1934
+ cli: { kind: "built", commands: ["naro backend keys"] },
1935
+ mcp: {
1936
+ kind: "excluded",
1937
+ reason: "secret-value",
1938
+ note: "service \uD0A4\uAC00 \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1939
+ }
1940
+ },
1941
+ {
1942
+ id: "backend-keys-rotate",
1943
+ domain: "backend",
1944
+ title: "API \uD0A4 \uAD50\uCCB4",
1945
+ routes: ["POST projects/[projectId]/backend/keys/rotate"],
1946
+ // 2026-11-04 전에는 화면이 없었는데 built로 적혀 있었다.
1947
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1948
+ ui: { kind: "built" },
1949
+ cli: { kind: "built", commands: ["naro backend keys rotate"] },
1950
+ mcp: {
1951
+ kind: "excluded",
1952
+ reason: "irreversible",
1953
+ note: "\uC61B \uD0A4\uAC00 \uBC14\uB85C \uC8FD\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1954
+ }
1955
+ },
1956
+ {
1957
+ id: "backend-signing-key-rotate",
1958
+ domain: "backend",
1959
+ title: "\uC11C\uBA85 \uD0A4 \uAD50\uCCB4",
1960
+ routes: ["POST projects/[projectId]/backend/signing-key/rotate"],
1961
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1962
+ ui: { kind: "built" },
1963
+ cli: { kind: "built", commands: ["naro backend signing-key rotate"] },
1964
+ mcp: {
1965
+ kind: "excluded",
1966
+ reason: "irreversible",
1967
+ note: "\uBC1C\uAE09\uD55C \uD1A0\uD070\uC774 15\uBD84 \uC548\uC5D0 \uBAA8\uB450 \uC8FD\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1968
+ }
1969
+ },
1970
+ {
1971
+ id: "backend-auth-get",
1972
+ domain: "backend",
1973
+ title: "\uB85C\uADF8\uC778 \uC124\uC815 \uC870\uD68C",
1974
+ routes: ["GET projects/[projectId]/backend/auth"],
1975
+ ui: { kind: "built" },
1976
+ cli: {
1977
+ kind: "built",
1978
+ commands: ["naro backend auth", "naro backend auth redirects"]
1979
+ },
1980
+ mcp: { kind: "built", tools: ["get_backend_auth"] }
1981
+ },
1982
+ {
1983
+ id: "backend-auth-update",
1984
+ domain: "backend",
1985
+ title: "\uB85C\uADF8\uC778 \uC124\uC815 \uBCC0\uACBD",
1986
+ routes: ["PUT projects/[projectId]/backend/auth"],
1987
+ ui: { kind: "built" },
1988
+ cli: {
1989
+ kind: "built",
1990
+ commands: [
1991
+ "naro backend auth signups",
1992
+ "naro backend auth password",
1993
+ "naro backend auth email set",
1994
+ "naro backend auth oauth set",
1995
+ "naro backend auth redirects add",
1996
+ "naro backend auth redirects remove",
1997
+ "naro backend auth require-verification"
1998
+ ]
1999
+ },
2000
+ // 리다이렉트 제거·가입·확인 메일·비밀번호 길이만. 메일 API 키와
2001
+ // OAuth client secret은 대화에 남고(secret-value), 리다이렉트 추가는 재설정 토큰이
2002
+ // 그 origin으로 가므로(PR #434 리뷰) 대시보드와 CLI만 한다.
2003
+ mcp: { kind: "built", tools: ["update_backend_auth"] }
2004
+ },
2005
+ {
2006
+ id: "backend-auth-remove",
2007
+ domain: "backend",
2008
+ title: "\uB85C\uADF8\uC778 \uACF5\uAE09\uC790\xB7\uBA54\uC77C \uC124\uC815 \uC81C\uAC70",
2009
+ routes: ["DELETE projects/[projectId]/backend/auth"],
2010
+ ui: { kind: "built" },
2011
+ cli: {
2012
+ kind: "built",
2013
+ commands: [
2014
+ "naro backend auth email remove",
2015
+ "naro backend auth oauth remove"
2016
+ ]
2017
+ },
2018
+ mcp: { kind: "built", tools: ["remove_backend_auth"] }
2019
+ },
2020
+ {
2021
+ id: "backend-user-list",
2022
+ domain: "backend",
2023
+ title: "\uC720\uC800 \uBAA9\uB85D",
2024
+ routes: ["GET projects/[projectId]/backend/users"],
2025
+ ui: { kind: "built" },
2026
+ cli: { kind: "built", commands: ["naro backend users"] },
2027
+ mcp: { kind: "built", tools: ["list_backend_users"] }
2028
+ },
2029
+ {
2030
+ id: "backend-user-create",
2031
+ domain: "backend",
2032
+ title: "\uC720\uC800 \uB9CC\uB4E4\uAE30",
2033
+ routes: ["POST projects/[projectId]/backend/users"],
2034
+ dashboard: "/[teamSlug]/projects/[projectSlug]/auth",
2035
+ ui: { kind: "built" },
2036
+ cli: { kind: "built", commands: ["naro backend users create"] },
2037
+ mcp: {
2038
+ kind: "excluded",
2039
+ reason: "secret-value",
2040
+ note: "\uBE44\uBC00\uBC88\uD638\uAC00 \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
2041
+ }
2042
+ },
2043
+ {
2044
+ id: "backend-user-delete",
2045
+ domain: "backend",
2046
+ title: "\uC720\uC800 \uC0AD\uC81C",
2047
+ routes: ["DELETE projects/[projectId]/backend/users/[userId]"],
2048
+ dashboard: "/[teamSlug]/projects/[projectSlug]/auth",
2049
+ ui: { kind: "built" },
2050
+ cli: { kind: "built", commands: ["naro backend users delete"] },
2051
+ mcp: {
2052
+ kind: "excluded",
2053
+ reason: "irreversible",
2054
+ note: "\uC720\uC800\uC640 \uC138\uC158\uC774 \uC0AC\uB77C\uC9C4\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
2055
+ }
2056
+ },
2057
+ {
2058
+ id: "logs-runtime",
2059
+ domain: "logs",
2060
+ title: "\uB7F0\uD0C0\uC784 \uB85C\uADF8",
2061
+ routes: ["GET projects/[projectId]/logs"],
2062
+ ui: { kind: "built" },
2063
+ cli: { kind: "built", commands: ["naro logs"] },
2064
+ mcp: { kind: "built", tools: ["get_runtime_logs"] }
2065
+ },
2066
+ {
2067
+ id: "analytics",
2068
+ domain: "measurement",
2069
+ title: "Web Analytics",
2070
+ routes: ["GET projects/[projectId]/analytics"],
2071
+ ui: { kind: "built" },
2072
+ cli: { kind: "built", commands: ["naro analytics"] },
2073
+ mcp: { kind: "built", tools: ["get_analytics"] }
2074
+ },
2075
+ {
2076
+ id: "insights",
2077
+ domain: "measurement",
2078
+ title: "Speed Insights",
2079
+ routes: ["GET projects/[projectId]/speed-insights"],
2080
+ ui: { kind: "built" },
2081
+ cli: { kind: "built", commands: ["naro insights"] },
2082
+ mcp: { kind: "built", tools: ["get_speed_insights"] }
2083
+ },
2084
+ {
2085
+ id: "insights-toggle",
2086
+ domain: "measurement",
2087
+ title: "Speed Insights \uCE21\uC815 \uCF1C\uAE30\xB7\uB044\uAE30",
2088
+ routes: ["PUT projects/[projectId]/speed-insights"],
2089
+ ui: { kind: "built" },
2090
+ cli: {
2091
+ kind: "built",
2092
+ commands: ["naro insights enable", "naro insights disable"]
2093
+ },
2094
+ mcp: { kind: "built", tools: ["set_speed_insights"] }
2095
+ },
2096
+ {
2097
+ id: "insights-deployments",
2098
+ domain: "measurement",
2099
+ title: "Speed Insights \uBC30\uD3EC\uBCC4 \uBE44\uAD50",
2100
+ routes: ["GET projects/[projectId]/speed-insights/deployments"],
2101
+ ui: { kind: "built" },
2102
+ // naro insights가 배포별 표를 같이 읽는다(client.insights.overview).
2103
+ cli: { kind: "built" },
2104
+ // get_speed_insights가 route 없이 배포별 표를 같이 돌려준다(client.insights.overview).
2105
+ mcp: { kind: "built" }
2106
+ },
2107
+ {
2108
+ id: "insights-paths",
2109
+ domain: "measurement",
2110
+ title: "Speed Insights \uB77C\uC6B0\uD2B8\uBCC4 \uACBD\uB85C",
2111
+ routes: ["GET projects/[projectId]/speed-insights/paths"],
2112
+ ui: { kind: "built" },
2113
+ // naro insights --route가 읽는다(client.insights.paths).
2114
+ cli: { kind: "built" },
2115
+ // get_speed_insights에 route를 주면 경로를 돌려준다(client.insights.paths).
2116
+ mcp: { kind: "built" }
2117
+ },
2118
+ {
2119
+ id: "observability",
2120
+ domain: "measurement",
2121
+ title: "Observability",
2122
+ routes: ["GET projects/[projectId]/observability"],
2123
+ ui: { kind: "built" },
2124
+ cli: { kind: "built", commands: ["naro observability"] },
2125
+ mcp: { kind: "built", tools: ["get_observability"] }
2126
+ },
2127
+ {
2128
+ id: "firewall-list",
2129
+ domain: "firewall",
2130
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uBAA9\uB85D",
2131
+ routes: ["GET projects/[projectId]/firewall"],
2132
+ ui: { kind: "built" },
2133
+ cli: { kind: "built", commands: ["naro firewall"] },
2134
+ mcp: { kind: "built", tools: ["list_firewall_rules"] }
2135
+ },
2136
+ {
2137
+ id: "firewall-add",
2138
+ domain: "firewall",
2139
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uCD94\uAC00",
2140
+ routes: ["POST projects/[projectId]/firewall"],
2141
+ ui: { kind: "built" },
2142
+ cli: { kind: "built", commands: ["naro firewall add"] },
2143
+ mcp: { kind: "built", tools: ["add_firewall_rule"] }
2144
+ },
2145
+ {
2146
+ id: "firewall-update",
2147
+ domain: "firewall",
2148
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uC218\uC815",
2149
+ routes: ["PATCH projects/[projectId]/firewall/[ruleId]"],
2150
+ ui: { kind: "built" },
2151
+ cli: {
2152
+ kind: "built",
2153
+ commands: ["naro firewall enable", "naro firewall disable"]
2154
+ },
2155
+ mcp: { kind: "built", tools: ["set_firewall_rule_enabled"] }
2156
+ },
2157
+ {
2158
+ id: "firewall-remove",
2159
+ domain: "firewall",
2160
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uC0AD\uC81C",
2161
+ routes: ["DELETE projects/[projectId]/firewall/[ruleId]"],
2162
+ ui: { kind: "built" },
2163
+ cli: { kind: "built", commands: ["naro firewall remove"] },
2164
+ mcp: { kind: "built", tools: ["remove_firewall_rule"] }
2165
+ },
2166
+ {
2167
+ id: "flag-list",
2168
+ domain: "flags",
2169
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uBAA9\uB85D",
2170
+ routes: ["GET projects/[projectId]/flags"],
2171
+ ui: { kind: "built" },
2172
+ cli: { kind: "built", commands: ["naro flags"] },
2173
+ mcp: { kind: "built", tools: ["list_flags"] }
2174
+ },
2175
+ {
2176
+ id: "flag-add",
2177
+ domain: "flags",
2178
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uCD94\uAC00",
2179
+ routes: ["POST projects/[projectId]/flags"],
2180
+ ui: { kind: "built" },
2181
+ cli: { kind: "built", commands: ["naro flags add"] },
2182
+ mcp: { kind: "built", tools: ["add_flag"] }
2183
+ },
2184
+ {
2185
+ id: "flag-update",
2186
+ domain: "flags",
2187
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uC218\uC815",
2188
+ routes: ["PATCH projects/[projectId]/flags/[flagId]"],
2189
+ ui: { kind: "built" },
2190
+ cli: { kind: "built", commands: ["naro flags on", "naro flags off"] },
2191
+ mcp: { kind: "built", tools: ["set_flag"] }
2192
+ },
2193
+ {
2194
+ id: "flag-remove",
2195
+ domain: "flags",
2196
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uC0AD\uC81C",
2197
+ routes: ["DELETE projects/[projectId]/flags/[flagId]"],
2198
+ ui: { kind: "built" },
2199
+ cli: { kind: "built", commands: ["naro flags remove"] },
2200
+ mcp: { kind: "built", tools: ["remove_flag"] }
2201
+ },
2202
+ {
2203
+ id: "dashboard-link",
2204
+ domain: "guidance",
2205
+ title: "CLI\xB7MCP\uB85C \uBABB \uD558\uB294 \uAE30\uB2A5\uC758 \uB300\uC2DC\uBCF4\uB4DC \uB9C1\uD06C",
2206
+ ui: {
2207
+ kind: "excluded",
2208
+ reason: "dashboard-itself",
2209
+ note: "\uC0AC\uC6A9\uC790\uAC00 \uC774\uBBF8 \uB300\uC2DC\uBCF4\uB4DC\uC5D0 \uC788\uB2E4."
2210
+ },
2211
+ cli: { kind: "built", commands: ["naro dashboard"] },
2212
+ mcp: { kind: "built", tools: ["dashboard_link"] }
2213
+ }
2214
+ ];
2215
+ function dashboardNeeds(path) {
2216
+ return {
2217
+ team: path.includes("[teamSlug]"),
2218
+ project: path.includes("[projectSlug]")
2219
+ };
2220
+ }
2221
+ function dashboardUrl(baseUrl, path, slugs) {
2222
+ const filled2 = path.replace("[teamSlug]", () => {
2223
+ if (!slugs.teamSlug) throw new Error(`${path} needs a team slug`);
2224
+ return encodeURIComponent(slugs.teamSlug);
2225
+ }).replace("[projectSlug]", () => {
2226
+ if (!slugs.projectSlug) throw new Error(`${path} needs a project slug`);
2227
+ return encodeURIComponent(slugs.projectSlug);
2228
+ });
2229
+ return `${baseUrl.replace(/\/+$/, "")}${filled2}`;
2230
+ }
2231
+
320
2232
  // ../../packages/core/src/deployment.ts
321
2233
  var PHASES = [
322
2234
  "QUEUED",
@@ -16369,10 +18281,13 @@ export default {
16369
18281
  },
16370
18282
  };
16371
18283
  `;
16372
- var OUTPUT_DIR = "dist";
18284
+ var VINEXT_OUTPUT_DIR = "dist";
16373
18285
  var SERVER_DIR = "server";
16374
18286
  var CLIENT_DIR = "client";
16375
18287
  var WRANGLER_OUTPUT_NAME = "wrangler.json";
18288
+ function vinextWranglerConfigPath(appRoot) {
18289
+ return join6(appRoot, VINEXT_OUTPUT_DIR, SERVER_DIR, WRANGLER_OUTPUT_NAME);
18290
+ }
16376
18291
  async function exists3(path) {
16377
18292
  try {
16378
18293
  await access5(path);
@@ -16428,7 +18343,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
16428
18343
  compatibility_flags: ["nodejs_compat"],
16429
18344
  main: `./${GENERATED_WORKER_ENTRY}`,
16430
18345
  assets: {
16431
- directory: `${OUTPUT_DIR}/${CLIENT_DIR}`,
18346
+ directory: `${VINEXT_OUTPUT_DIR}/${CLIENT_DIR}`,
16432
18347
  binding: "ASSETS"
16433
18348
  }
16434
18349
  },
@@ -16527,14 +18442,14 @@ function moduleMatchers(rules) {
16527
18442
  }
16528
18443
  async function readVinextArtifact(appRoot, identity) {
16529
18444
  try {
16530
- const outputRoot = resolve2(appRoot, OUTPUT_DIR);
18445
+ const outputRoot = resolve2(appRoot, VINEXT_OUTPUT_DIR);
16531
18446
  const serverRoot = join6(outputRoot, SERVER_DIR);
16532
18447
  const clientRoot = join6(outputRoot, CLIENT_DIR);
16533
18448
  const configPath = join6(serverRoot, WRANGLER_OUTPUT_NAME);
16534
18449
  if (!await exists3(configPath)) {
16535
18450
  throw new DeploymentError(
16536
18451
  "ARTIFACT_INVALID",
16537
- `vinext build produced no ${OUTPUT_DIR}/${SERVER_DIR}/${WRANGLER_OUTPUT_NAME}.`
18452
+ `vinext build produced no ${VINEXT_OUTPUT_DIR}/${SERVER_DIR}/${WRANGLER_OUTPUT_NAME}.`
16538
18453
  );
16539
18454
  }
16540
18455
  const metadata = JSON.parse(
@@ -18606,13 +20521,8 @@ function createAwsMiddlewareCloudflareAppStrategy() {
18606
20521
  }
18607
20522
 
18608
20523
  // ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
18609
- import { join as join16 } from "path";
18610
20524
  var VINEXT_MIN_NEXT_MAJOR = 16;
18611
20525
  var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
18612
- var VINEXT_OUTPUT = {
18613
- outputRoot: "dist",
18614
- bundleDir: "dist/.naro-bundle"
18615
- };
18616
20526
  async function runCheck(context, observer) {
18617
20527
  if (!context.toolchain.vinext) return null;
18618
20528
  const held = [];
@@ -18762,13 +20672,8 @@ function createVinextCloudflareStrategy(options) {
18762
20672
  context,
18763
20673
  {
18764
20674
  appRoot: fork.appRoot,
18765
- wranglerConfigPath: join16(
18766
- fork.appRoot,
18767
- "dist",
18768
- "server",
18769
- "wrangler.json"
18770
- ),
18771
- ...VINEXT_OUTPUT
20675
+ wranglerConfigPath: vinextWranglerConfigPath(fork.appRoot),
20676
+ outputRoot: VINEXT_OUTPUT_DIR
18772
20677
  },
18773
20678
  observer
18774
20679
  );
@@ -18787,7 +20692,7 @@ function createVinextCloudflareStrategy(options) {
18787
20692
  // ../../packages/build/src/strategy/workspace.ts
18788
20693
  import { execFile } from "child_process";
18789
20694
  import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
18790
- import { basename as basename2, dirname as dirname3, join as join17, relative as relative5, resolve as resolve6 } from "path";
20695
+ import { basename as basename2, dirname as dirname3, join as join16, relative as relative5, resolve as resolve6 } from "path";
18791
20696
  import { promisify } from "util";
18792
20697
  var execFileAsync = promisify(execFile);
18793
20698
  var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
@@ -18810,14 +20715,14 @@ async function copyInstalledPackages(source, target2) {
18810
20715
  const walk2 = async (dir) => {
18811
20716
  for (const entry of await readdir4(dir, { withFileTypes: true })) {
18812
20717
  if (!entry.isDirectory() || entry.name === ".git") continue;
18813
- const path = join17(dir, entry.name);
20718
+ const path = join16(dir, entry.name);
18814
20719
  if (entry.name === "node_modules") installs.push(path);
18815
20720
  else await walk2(path);
18816
20721
  }
18817
20722
  };
18818
20723
  await walk2(origin);
18819
20724
  for (const install of installs)
18820
- await copyTree(install, join17(target2, relative5(origin, install)));
20725
+ await copyTree(install, join16(target2, relative5(origin, install)));
18821
20726
  }
18822
20727
  async function copyTree(from, to) {
18823
20728
  await mkdir2(dirname3(to), { recursive: true });
@@ -18832,7 +20737,7 @@ async function copyTree(from, to) {
18832
20737
  await cp(from, to, { recursive: true, verbatimSymlinks: true });
18833
20738
  }
18834
20739
  function forkedAppRoot(checkout, appRoot, forkCheckout) {
18835
- return join17(forkCheckout, relative5(checkout, appRoot));
20740
+ return join16(forkCheckout, relative5(checkout, appRoot));
18836
20741
  }
18837
20742
 
18838
20743
  // ../../packages/build/src/local-build-executor.ts
@@ -18855,7 +20760,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
18855
20760
  }
18856
20761
  }
18857
20762
  var defaultLocalBuildDependencies = {
18858
- createWorkspace: () => mkdtemp(join18(tmpdir(), "next-deploy-build-")),
20763
+ createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
18859
20764
  removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
18860
20765
  runCommand,
18861
20766
  detectPackageManager,
@@ -18890,7 +20795,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
18890
20795
  for (; ; ) {
18891
20796
  try {
18892
20797
  const source = await readFile13(
18893
- join18(dir, "node_modules", "next", "package.json"),
20798
+ join17(dir, "node_modules", "next", "package.json"),
18894
20799
  "utf8"
18895
20800
  );
18896
20801
  const version3 = JSON.parse(source).version;
@@ -19045,8 +20950,8 @@ var LocalBuildExecutor = class {
19045
20950
  }
19046
20951
  async build(input, observer, cache) {
19047
20952
  const workspace = await this.dependencies.createWorkspace();
19048
- const checkout = join18(workspace, "checkout");
19049
- const pmCacheDir = join18(workspace, ".pm-store");
20953
+ const checkout = join17(workspace, "checkout");
20954
+ const pmCacheDir = join17(workspace, ".pm-store");
19050
20955
  let cacheSeq = 0;
19051
20956
  const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
19052
20957
  const compressor = "zstd";
@@ -19055,7 +20960,7 @@ var LocalBuildExecutor = class {
19055
20960
  transport: cache,
19056
20961
  fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
19057
20962
  runCommand: this.dependencies.runCommand,
19058
- tmpFile: (suffix) => join18(workspace, `cache-${String(cacheSeq++)}${suffix}`),
20963
+ tmpFile: (suffix) => join17(workspace, `cache-${String(cacheSeq++)}${suffix}`),
19059
20964
  // One latch for the whole build so a disabled cache warns once.
19060
20965
  report: { unavailable: false }
19061
20966
  } : null;
@@ -19263,7 +21168,7 @@ var LocalBuildExecutor = class {
19263
21168
  }
19264
21169
  }
19265
21170
  await notifyStatus(observer, "BUILDING");
19266
- const buildCacheDir = resolved.preset.cachePath ? join18(appRoot, resolved.preset.cachePath) : null;
21171
+ const buildCacheDir = resolved.preset.cachePath ? join17(appRoot, resolved.preset.cachePath) : null;
19267
21172
  const buildCacheKey = sanitizeCacheKey(input.branch);
19268
21173
  if (cachePorts && buildCacheDir) {
19269
21174
  await tryRestore(
@@ -19287,13 +21192,13 @@ var LocalBuildExecutor = class {
19287
21192
  entries: fingerprintEntries,
19288
21193
  nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
19289
21194
  });
19290
- const pristine = join18(workspace, "pristine");
21195
+ const pristine = join17(workspace, "pristine");
19291
21196
  const fork = this.dependencies.forkWorkspace ?? defaultFork;
19292
21197
  await fork(checkout, pristine);
19293
21198
  let forks = 0;
19294
21199
  const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
19295
21200
  const forkWorkspace = async (label2) => {
19296
- const target2 = join18(
21201
+ const target2 = join17(
19297
21202
  workspace,
19298
21203
  "forks",
19299
21204
  `${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
@@ -19589,12 +21494,12 @@ async function errorFromResponse(response, context) {
19589
21494
  // ../../packages/sdk/src/session.ts
19590
21495
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
19591
21496
  import { homedir } from "os";
19592
- import { join as join19 } from "path";
21497
+ import { join as join18 } from "path";
19593
21498
  function credentialsDir(home = homedir()) {
19594
- return join19(home, ".naro");
21499
+ return join18(home, ".naro");
19595
21500
  }
19596
21501
  function credentialsFile(home = homedir()) {
19597
- return join19(credentialsDir(home), "credentials.json");
21502
+ return join18(credentialsDir(home), "credentials.json");
19598
21503
  }
19599
21504
  function saveSession(session, home = homedir()) {
19600
21505
  mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
@@ -20198,44 +22103,6 @@ var FlagsApi = class {
20198
22103
  );
20199
22104
  }
20200
22105
  };
20201
- var BindingsApi = class {
20202
- constructor(client) {
20203
- this.client = client;
20204
- }
20205
- async list(projectId) {
20206
- const body = await this.client.request(
20207
- project(projectId, "/storage"),
20208
- { context: "List storage bindings" }
20209
- );
20210
- return body.storage;
20211
- }
20212
- /**
20213
- * Makes the Cloudflare resource and binds it at the next deploy.
20214
- * `VALIDATION_FAILED` for a bad type or binding name (ASSETS and DB are
20215
- * reserved), `STORAGE_PROVISION_FAILED` (502) when Cloudflare refused.
20216
- */
20217
- async add(projectId, type, bindingName) {
20218
- const answer = await this.client.request(
20219
- project(projectId, "/storage"),
20220
- {
20221
- method: "POST",
20222
- body: { type, bindingName },
20223
- context: "Add storage binding"
20224
- }
20225
- );
20226
- return answer.storage;
20227
- }
20228
- /**
20229
- * Deletes the Cloudflare resource and what it holds — it cannot be undone.
20230
- * `STORAGE_MANAGED` refuses the Naro database.
20231
- */
20232
- async remove(projectId, bindingId) {
20233
- await this.client.request(
20234
- project(projectId, `/storage/${encodeURIComponent(bindingId)}`),
20235
- { method: "DELETE", context: "Remove storage binding" }
20236
- );
20237
- }
20238
- };
20239
22106
 
20240
22107
  // ../../packages/sdk/src/ops.ts
20241
22108
  var BillingApi = class {
@@ -20432,7 +22299,6 @@ var NaroClient = class _NaroClient {
20432
22299
  dns;
20433
22300
  firewall;
20434
22301
  flags;
20435
- bindings;
20436
22302
  billing;
20437
22303
  observability;
20438
22304
  supabase;
@@ -20462,7 +22328,6 @@ var NaroClient = class _NaroClient {
20462
22328
  this.dns = new DnsApi(this);
20463
22329
  this.firewall = new FirewallApi(this);
20464
22330
  this.flags = new FlagsApi(this);
20465
- this.bindings = new BindingsApi(this);
20466
22331
  this.billing = new BillingApi(this);
20467
22332
  this.observability = new ObservabilityApi(this);
20468
22333
  this.supabase = new SupabaseApi(this);
@@ -20987,9 +22852,10 @@ var EnvApi = class {
20987
22852
  /**
20988
22853
  * The names the env route refuses as a key — `409 VALIDATION_FAILED`
20989
22854
  * `<KEY> is already used by a storage binding.` — read the way it reads
20990
- * them: `ASSETS` (the static files), every binding of the project's storage
20991
- * and, on a project using the managed database (`database_provider` is
20992
- * `d1`), `DB`, which may not be a storage row yet. For a caller that sends
22855
+ * them: `ASSETS` (the static files) and, on a project using the managed
22856
+ * database (`database_provider` is `d1`), `DB`. The bindings a user
22857
+ * declared (R2·KV·D1) are gone since 2026-11-04, and so is the route that
22858
+ * listed them. For a caller that sends
20993
22859
  * many entries in several requests (`naro env import`) and has to refuse
20994
22860
  * before the first one is saved. A control plane whose project row has no
20995
22861
  * `database_provider` reads as having no managed database, so `DB` is only
@@ -20997,20 +22863,8 @@ var EnvApi = class {
20997
22863
  */
20998
22864
  async takenBindingNames(projectId) {
20999
22865
  const base2 = `/api/projects/${encodeURIComponent(projectId)}`;
21000
- const [storage, project2] = await Promise.all([
21001
- this.client.request(
21002
- `${base2}/storage`,
21003
- { context: "List storage" }
21004
- ),
21005
- this.client.request(
21006
- base2,
21007
- { context: "Read project" }
21008
- )
21009
- ]);
21010
- const names = /* @__PURE__ */ new Set([
21011
- STATIC_ASSETS_BINDING,
21012
- ...storage.storage.map((row) => row.bindingName)
21013
- ]);
22866
+ const project2 = await this.client.request(base2, { context: "Read project" });
22867
+ const names = /* @__PURE__ */ new Set([STATIC_ASSETS_BINDING]);
21014
22868
  if (project2.project.database_provider === "d1")
21015
22869
  names.add(MANAGED_DATABASE_BINDING);
21016
22870
  return [...names];
@@ -22686,6 +24540,54 @@ var FunctionsApi = class {
22686
24540
  }
22687
24541
  };
22688
24542
 
24543
+ // ../../packages/sdk/src/dashboard.ts
24544
+ function dashboardCapability(id) {
24545
+ const found = CAPABILITIES.find((capability) => capability.id === id);
24546
+ if (!found || typeof found.dashboard !== "string")
24547
+ throw new NaroError(
24548
+ "VALIDATION_FAILED",
24549
+ `No dashboard page for "${id}". Run without a capability to list the ones that have one.`
24550
+ );
24551
+ return found;
24552
+ }
24553
+ async function dashboardLink(client, capability, target2, options) {
24554
+ const needs = dashboardNeeds(capability.dashboard);
24555
+ let teamSlug = target2.teamSlug;
24556
+ let projectSlug;
24557
+ if (needs.project) {
24558
+ if (!target2.projectId)
24559
+ throw new NaroError(
24560
+ "PROJECT_NOT_LINKED",
24561
+ "This page belongs to a project: pass its id, or link the directory first."
24562
+ );
24563
+ const [project2, teams] = await Promise.all([
24564
+ client.projects.get(target2.projectId),
24565
+ client.teams.list()
24566
+ ]);
24567
+ const team = teams.find((candidate) => candidate.id === project2.teamId);
24568
+ if (!team)
24569
+ throw new NaroError(
24570
+ "NOT_FOUND",
24571
+ `The team of project ${project2.slug} is not one of yours.`
24572
+ );
24573
+ teamSlug = team.slug;
24574
+ projectSlug = project2.slug;
24575
+ } else if (needs.team && !teamSlug) {
24576
+ throw new NaroError("TEAM_REQUIRED", "This page belongs to a team.");
24577
+ }
24578
+ const support = capability[options.client];
24579
+ return {
24580
+ capability: capability.id,
24581
+ title: capability.title,
24582
+ url: dashboardUrl(options.baseUrl, capability.dashboard, {
24583
+ teamSlug,
24584
+ projectSlug
24585
+ }),
24586
+ reason: support.kind === "excluded" ? { code: support.reason, why: REASON_CODES[support.reason].why } : null,
24587
+ cli: capability.cli.kind === "built" ? capability.cli.commands ?? [] : []
24588
+ };
24589
+ }
24590
+
22689
24591
  // ../../packages/sdk/src/database.ts
22690
24592
  var BOOKMARK_RE = /^[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{32}$/;
22691
24593
  var BOOKMARK_FORMAT_MESSAGE = "bookmark must look like 00000001-00000008-000050ed-272153b8520cfb93b49834d93a1a1ab3 (four hyphen-separated hex groups, 59 characters).";
@@ -22736,14 +24638,14 @@ function summarizeStatement(statement2) {
22736
24638
 
22737
24639
  // ../../packages/sdk/src/dotenv.ts
22738
24640
  import { existsSync, readFileSync as readFileSync2 } from "fs";
22739
- import { join as join20 } from "path";
24641
+ import { join as join19 } from "path";
22740
24642
  var QUOTES2 = /* @__PURE__ */ new Set(["'", '"', "`"]);
22741
24643
  function parseDotenv(text3) {
22742
24644
  return parseDotenvDetailed(text3).vars;
22743
24645
  }
22744
24646
  function readProjectEnv(dir) {
22745
24647
  for (const name of [".env.local", ".env.production", ".env"]) {
22746
- const path = join20(dir, name);
24648
+ const path = join19(dir, name);
22747
24649
  if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
22748
24650
  }
22749
24651
  return {};
@@ -22797,7 +24699,7 @@ function serializeDotenv(vars) {
22797
24699
 
22798
24700
  // ../../packages/sdk/src/env-scan.ts
22799
24701
  import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
22800
- import { join as join21 } from "path";
24702
+ import { join as join20 } from "path";
22801
24703
  var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
22802
24704
  ".ts",
22803
24705
  ".tsx",
@@ -22852,7 +24754,7 @@ function collect(dir, out) {
22852
24754
  for (const entry of entries) {
22853
24755
  if (out.length >= MAX_FILES) return;
22854
24756
  if (entry.isSymbolicLink()) continue;
22855
- const path = join21(dir, entry.name);
24757
+ const path = join20(dir, entry.name);
22856
24758
  if (entry.isDirectory()) {
22857
24759
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
22858
24760
  continue;
@@ -22884,7 +24786,7 @@ function scanEnvReferences(dir) {
22884
24786
  }
22885
24787
  }
22886
24788
  for (const name of EXAMPLE_FILES) {
22887
- const path = join21(dir, name);
24789
+ const path = join20(dir, name);
22888
24790
  if (!existsSync2(path)) continue;
22889
24791
  let parsed;
22890
24792
  try {
@@ -22902,11 +24804,11 @@ function scanEnvReferences(dir) {
22902
24804
 
22903
24805
  // ../../packages/sdk/src/framework.ts
22904
24806
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
22905
- import { join as join22 } from "path";
24807
+ import { join as join21 } from "path";
22906
24808
  function readPackageJson2(dir) {
22907
24809
  try {
22908
24810
  return JSON.parse(
22909
- readFileSync4(join22(dir, "package.json"), "utf8")
24811
+ readFileSync4(join21(dir, "package.json"), "utf8")
22910
24812
  );
22911
24813
  } catch {
22912
24814
  return null;
@@ -22917,7 +24819,7 @@ function hasDependency(pkg, name) {
22917
24819
  }
22918
24820
  function hasConfig(dir, base2, evidence) {
22919
24821
  for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
22920
- if (existsSync3(join22(dir, `${base2}.${ext}`))) {
24822
+ if (existsSync3(join21(dir, `${base2}.${ext}`))) {
22921
24823
  evidence.push(`${base2}.${ext}`);
22922
24824
  return true;
22923
24825
  }
@@ -23025,8 +24927,8 @@ function detectFramework2(dir) {
23025
24927
  if (!byDependency && !byConfig) continue;
23026
24928
  if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
23027
24929
  if (detector.framework === "nextjs") {
23028
- if (existsSync3(join22(dir, "app"))) evidence.push("app/");
23029
- if (existsSync3(join22(dir, "pages"))) evidence.push("pages/");
24930
+ if (existsSync3(join21(dir, "app"))) evidence.push("app/");
24931
+ if (existsSync3(join21(dir, "pages"))) evidence.push("pages/");
23030
24932
  }
23031
24933
  return {
23032
24934
  framework: detector.framework,
@@ -23037,7 +24939,7 @@ function detectFramework2(dir) {
23037
24939
  deployable: true
23038
24940
  };
23039
24941
  }
23040
- if (!pkg && existsSync3(join22(dir, "index.html"))) {
24942
+ if (!pkg && existsSync3(join21(dir, "index.html"))) {
23041
24943
  evidence.push("index.html (no package.json)");
23042
24944
  return {
23043
24945
  framework: "static",
@@ -31847,12 +33749,12 @@ import {
31847
33749
  rmSync as rmSync2,
31848
33750
  writeFileSync as writeFileSync2
31849
33751
  } from "fs";
31850
- import { join as join23 } from "path";
33752
+ import { join as join22 } from "path";
31851
33753
  function linkDir(dir) {
31852
- return join23(dir, ".naro");
33754
+ return join22(dir, ".naro");
31853
33755
  }
31854
33756
  function linkFile(dir) {
31855
- return join23(linkDir(dir), "project.json");
33757
+ return join22(linkDir(dir), "project.json");
31856
33758
  }
31857
33759
  function readProjectLink(dir) {
31858
33760
  try {
@@ -31874,7 +33776,7 @@ function writeProjectLink(dir, link) {
31874
33776
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
31875
33777
  `);
31876
33778
  writeFileSync2(
31877
- join23(linkDir(dir), "README.txt"),
33779
+ join22(linkDir(dir), "README.txt"),
31878
33780
  "This directory links the project to Naro and should not be committed.\n"
31879
33781
  );
31880
33782
  }
@@ -31884,9 +33786,9 @@ function removeProjectLink(dir) {
31884
33786
  return true;
31885
33787
  }
31886
33788
  function ensureGitignoreHasNaro(dir) {
31887
- const gitignorePath = join23(dir, ".gitignore");
33789
+ const gitignorePath = join22(dir, ".gitignore");
31888
33790
  const hasGitignore = existsSync4(gitignorePath);
31889
- if (!hasGitignore && !existsSync4(join23(dir, ".git"))) return false;
33791
+ if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
31890
33792
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
31891
33793
  const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
31892
33794
  if (covered) return false;
@@ -32671,7 +34573,7 @@ function usageStatus(report, currentMonth, requests) {
32671
34573
 
32672
34574
  // src/env-consent.ts
32673
34575
  import { existsSync as existsSync6 } from "fs";
32674
- import { join as join24 } from "path";
34576
+ import { join as join23 } from "path";
32675
34577
  var DOTENV_FILES = [".env.local", ".env.production", ".env"];
32676
34578
  var PUBLIC_PREFIX = "NEXT_PUBLIC_";
32677
34579
  function maskSecretValue(value) {
@@ -32683,7 +34585,7 @@ function maskEnvValue(key, value) {
32683
34585
  return maskSecretValue(value);
32684
34586
  }
32685
34587
  function findDotenvFile(dir) {
32686
- for (const name of DOTENV_FILES) if (existsSync6(join24(dir, name))) return name;
34588
+ for (const name of DOTENV_FILES) if (existsSync6(join23(dir, name))) return name;
32687
34589
  return null;
32688
34590
  }
32689
34591
  function envAddGuidance(keys, environment) {
@@ -32947,13 +34849,13 @@ async function uploadArtifact(artifact, opts) {
32947
34849
 
32948
34850
  // src/version.ts
32949
34851
  import { readFileSync as readFileSync6 } from "fs";
32950
- import { dirname as dirname5, join as join25 } from "path";
34852
+ import { dirname as dirname5, join as join24 } from "path";
32951
34853
  import { fileURLToPath } from "url";
32952
34854
  function version2() {
32953
34855
  try {
32954
34856
  const pkg = JSON.parse(
32955
34857
  readFileSync6(
32956
- join25(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"),
34858
+ join24(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"),
32957
34859
  "utf8"
32958
34860
  )
32959
34861
  );
@@ -32982,7 +34884,7 @@ import {
32982
34884
  basename as basename3,
32983
34885
  dirname as dirname6,
32984
34886
  isAbsolute as isAbsolute4,
32985
- join as join26,
34887
+ join as join25,
32986
34888
  relative as relative6,
32987
34889
  resolve as resolve10,
32988
34890
  sep as sep5
@@ -33109,12 +35011,12 @@ async function copyWorkingTree(source, checkout) {
33109
35011
  keptLinks,
33110
35012
  async ({ path, text: text3 }) => {
33111
35013
  try {
33112
- return inside(await realpath3(join26(checkout, path)), root);
35014
+ return inside(await realpath3(join25(checkout, path)), root);
33113
35015
  } catch (error51) {
33114
35016
  return error51.code === "ENOENT" && climbsOnlyFirst(text3);
33115
35017
  }
33116
35018
  },
33117
- ({ path }) => unlink(join26(checkout, path))
35019
+ ({ path }) => unlink(join25(checkout, path))
33118
35020
  );
33119
35021
  linksElsewhere.push(...strays.map(({ path }) => path));
33120
35022
  const committed = await committedEnvFiles(sourceDir);
@@ -33128,7 +35030,7 @@ async function copyWorkingTree(source, checkout) {
33128
35030
  outsideProject.push(path);
33129
35031
  continue;
33130
35032
  }
33131
- const target2 = join26(checkout, path);
35033
+ const target2 = join25(checkout, path);
33132
35034
  if (!inside(await realAncestor(dirname6(target2)), root)) {
33133
35035
  outsideProject.push(path);
33134
35036
  continue;
@@ -33153,16 +35055,16 @@ async function copyWorkingTree(source, checkout) {
33153
35055
  continue;
33154
35056
  }
33155
35057
  await writeFile7(target2, content, { flag: "wx" });
33156
- const local = await readLocal(join26(sourceDir, path));
35058
+ const local = await readLocal(join25(sourceDir, path));
33157
35059
  if (!local || !sameText(local, content)) committedVersion.push(path);
33158
35060
  }
33159
35061
  const envStrays = await keptOnly(
33160
35062
  envLinks,
33161
35063
  async (path) => {
33162
- const real = await realpath3(join26(checkout, path)).catch(() => null);
35064
+ const real = await realpath3(join25(checkout, path)).catch(() => null);
33163
35065
  return real !== null && inside(real, root);
33164
35066
  },
33165
- (path) => unlink(join26(checkout, path))
35067
+ (path) => unlink(join25(checkout, path))
33166
35068
  );
33167
35069
  outsideProject.push(...envStrays);
33168
35070
  const inCommit = new Set(
@@ -33215,7 +35117,7 @@ function forEnvironment(text3, environment) {
33215
35117
 
33216
35118
  // src/commands/migration.ts
33217
35119
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
33218
- import { join as join27, relative as relative7 } from "path";
35120
+ import { join as join26, relative as relative7 } from "path";
33219
35121
 
33220
35122
  // src/prompt.ts
33221
35123
  import { createInterface as createInterface2 } from "readline";
@@ -33389,7 +35291,7 @@ function warningLines(message) {
33389
35291
  }
33390
35292
  function policyProblems(dir, names) {
33391
35293
  return names.flatMap((name) => {
33392
- const split2 = splitStatements(readFileSync7(join27(dir, name), "utf8"));
35294
+ const split2 = splitStatements(readFileSync7(join26(dir, name), "utf8"));
33393
35295
  if (!split2.ok) return [];
33394
35296
  const planned = planPolicyMigration(
33395
35297
  split2.statements.map((statement2) => statement2.text)
@@ -33420,7 +35322,7 @@ async function runMigrationNew(context, deps = processStdin) {
33420
35322
  const body = fromStdin ? piped.endsWith("\n") ? piped : `${piped}
33421
35323
  ` : `-- Migration: ${fileName}
33422
35324
  `;
33423
- const path = join27(dir, fileName);
35325
+ const path = join26(dir, fileName);
33424
35326
  writeFileSync3(path, body, { flag: "wx" });
33425
35327
  const relPath = relativeToCwd(context, path);
33426
35328
  context.output.result({ file: fileName, path: relPath, fromStdin }, [
@@ -33449,7 +35351,7 @@ async function applyMigrationFiles(context, client, projectId, dir, names) {
33449
35351
  const applied = [];
33450
35352
  const warnings = [];
33451
35353
  for (const name of names) {
33452
- const sql = readFileSync7(join27(dir, name), "utf8");
35354
+ const sql = readFileSync7(join26(dir, name), "utf8");
33453
35355
  let answer;
33454
35356
  try {
33455
35357
  answer = await client.db.applyMigration(projectId, { name, sql });
@@ -33661,7 +35563,7 @@ var BUNDLE_MESSAGES = {
33661
35563
 
33662
35564
  // ../../packages/functions-bundler/src/discover.ts
33663
35565
  import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
33664
- import { join as join28, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
35566
+ import { join as join27, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
33665
35567
 
33666
35568
  // ../../packages/functions-bundler/src/limits.ts
33667
35569
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
@@ -33685,7 +35587,7 @@ function isEntrypoint(value) {
33685
35587
  function functionsFolder(options) {
33686
35588
  const root = resolve11(options.cwd, options.root ?? ".");
33687
35589
  const explicit = options.dir !== void 0;
33688
- const dir = explicit ? resolve11(options.cwd, options.dir) : join28(root, "naro", "functions");
35590
+ const dir = explicit ? resolve11(options.cwd, options.dir) : join27(root, "naro", "functions");
33689
35591
  const label2 = relative8(options.cwd, dir).split(sep6).join("/") || ".";
33690
35592
  return { dir, label: label2, root, explicit };
33691
35593
  }
@@ -33694,7 +35596,7 @@ function listFunctionNames(folder) {
33694
35596
  try {
33695
35597
  entries = readdirSync3(folder.dir, { withFileTypes: true });
33696
35598
  } catch {
33697
- if (!folder.explicit && existsSync7(join28(folder.root, "supabase", "functions")))
35599
+ if (!folder.explicit && existsSync7(join27(folder.root, "supabase", "functions")))
33698
35600
  throw new FunctionBundleError(
33699
35601
  "VALIDATION_FAILED",
33700
35602
  BUNDLE_MESSAGES.supabaseFound
@@ -33715,7 +35617,7 @@ function listFunctionNames(folder) {
33715
35617
  return names;
33716
35618
  }
33717
35619
  function readFunctionJson(dir, name) {
33718
- const file2 = join28(dir, "function.json");
35620
+ const file2 = join27(dir, "function.json");
33719
35621
  if (!existsSync7(file2)) return { verifyJwt: void 0, entrypoint: void 0 };
33720
35622
  let parsed;
33721
35623
  try {
@@ -33759,7 +35661,7 @@ function readFunctionSource(folder, name) {
33759
35661
  "VALIDATION_FAILED",
33760
35662
  BUNDLE_MESSAGES.badName(name)
33761
35663
  );
33762
- const dir = join28(folder.dir, name);
35664
+ const dir = join27(folder.dir, name);
33763
35665
  if (!statSync2(dir, { throwIfNoEntry: false })?.isDirectory())
33764
35666
  throw new FunctionBundleError(
33765
35667
  "VALIDATION_FAILED",
@@ -33768,14 +35670,14 @@ function readFunctionSource(folder, name) {
33768
35670
  const config2 = readFunctionJson(dir, name);
33769
35671
  let entrypoint;
33770
35672
  if (config2.entrypoint !== void 0) {
33771
- if (!existsSync7(join28(dir, ...config2.entrypoint.split("/"))))
35673
+ if (!existsSync7(join27(dir, ...config2.entrypoint.split("/"))))
33772
35674
  throw new FunctionBundleError(
33773
35675
  "FUNCTION_BUNDLE_FAILED",
33774
35676
  BUNDLE_MESSAGES.entryMissing(name, config2.entrypoint)
33775
35677
  );
33776
35678
  entrypoint = config2.entrypoint;
33777
35679
  } else {
33778
- const found = ENTRY_FILES.find((file2) => existsSync7(join28(dir, file2)));
35680
+ const found = ENTRY_FILES.find((file2) => existsSync7(join27(dir, file2)));
33779
35681
  if (found === void 0)
33780
35682
  throw new FunctionBundleError(
33781
35683
  "FUNCTION_BUNDLE_FAILED",
@@ -33786,14 +35688,14 @@ function readFunctionSource(folder, name) {
33786
35688
  return {
33787
35689
  name,
33788
35690
  dir,
33789
- entry: join28(dir, ...entrypoint.split("/")),
35691
+ entry: join27(dir, ...entrypoint.split("/")),
33790
35692
  entrypoint,
33791
35693
  verifyJwt: config2.verifyJwt
33792
35694
  };
33793
35695
  }
33794
35696
  function configTomlWarning(folder) {
33795
- if (!folder.dir.endsWith(join28("supabase", "functions"))) return null;
33796
- const file2 = join28(folder.dir, "..", "config.toml");
35697
+ if (!folder.dir.endsWith(join27("supabase", "functions"))) return null;
35698
+ const file2 = join27(folder.dir, "..", "config.toml");
33797
35699
  if (!existsSync7(file2)) return null;
33798
35700
  return /^\s*verify_jwt\s*=/m.test(readFileSync8(file2, "utf8")) ? BUNDLE_MESSAGES.supabaseVerifyJwt(folder.label) : null;
33799
35701
  }
@@ -33999,11 +35901,11 @@ ${injects.map(
33999
35901
 
34000
35902
  // ../../packages/functions-bundler/src/deno-imports.ts
34001
35903
  import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync as realpathSync2 } from "fs";
34002
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join30, relative as relative9, sep as sep8 } from "path";
35904
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join29, relative as relative9, sep as sep8 } from "path";
34003
35905
 
34004
35906
  // ../../packages/functions-bundler/src/import-map.ts
34005
35907
  import { existsSync as existsSync8, readFileSync as readFileSync9, realpathSync } from "fs";
34006
- import { basename as basename4, dirname as dirname8, join as join29, resolve as resolve12, sep as sep7 } from "path";
35908
+ import { basename as basename4, dirname as dirname8, join as join28, resolve as resolve12, sep as sep7 } from "path";
34007
35909
  import { fileURLToPath as fileURLToPath3 } from "url";
34008
35910
 
34009
35911
  // ../../packages/functions-bundler/src/printable.ts
@@ -34075,7 +35977,7 @@ function readImportMapFile(file2, label2) {
34075
35977
  }
34076
35978
  function readImportMap(functionDir, fallback, labelOf) {
34077
35979
  for (const name of FILES) {
34078
- const file2 = join29(functionDir, name);
35980
+ const file2 = join28(functionDir, name);
34079
35981
  if (!existsSync8(file2)) continue;
34080
35982
  const map2 = load(file2, labelOf(file2));
34081
35983
  if (map2 !== null) return map2;
@@ -34218,7 +36120,7 @@ function packageDir(from, projectRoot) {
34218
36120
  let current = from;
34219
36121
  while (isInside(current, projectRoot)) {
34220
36122
  const inNodeModules = fromRoot(projectRoot, current).split("/").includes("node_modules");
34221
- if (!inNodeModules && existsSync9(join30(current, "package.json")))
36123
+ if (!inNodeModules && existsSync9(join29(current, "package.json")))
34222
36124
  return fromRoot(projectRoot, current) || ".";
34223
36125
  const up = dirname9(current);
34224
36126
  if (up === current) break;
@@ -34230,7 +36132,7 @@ function isInstalled(from, pkg) {
34230
36132
  if (pkg === "") return false;
34231
36133
  let current = from;
34232
36134
  for (; ; ) {
34233
- if (existsSync9(join30(current, "node_modules", pkg))) return true;
36135
+ if (existsSync9(join29(current, "node_modules", pkg))) return true;
34234
36136
  const up = dirname9(current);
34235
36137
  if (up === current) return false;
34236
36138
  current = up;
@@ -34240,7 +36142,7 @@ var NODE_MODULES = /[\\/]node_modules[\\/]/;
34240
36142
  function installedVersion(resolved, pkg) {
34241
36143
  let current = dirname9(resolved);
34242
36144
  for (; ; ) {
34243
- const file2 = join30(current, "package.json");
36145
+ const file2 = join29(current, "package.json");
34244
36146
  if (existsSync9(file2)) {
34245
36147
  try {
34246
36148
  const manifest2 = JSON.parse(readFileSync10(file2, "utf8"));
@@ -34424,7 +36326,7 @@ function denoImportsPlugin(options) {
34424
36326
 
34425
36327
  // ../../packages/functions-bundler/src/bundle.ts
34426
36328
  import { createHash as createHash4 } from "crypto";
34427
- import { join as join31, relative as relative10, sep as sep9 } from "path";
36329
+ import { join as join30, relative as relative10, sep as sep9 } from "path";
34428
36330
  import { gzipSync } from "zlib";
34429
36331
 
34430
36332
  // ../../packages/functions-runtime/dist/index.js
@@ -34500,7 +36402,7 @@ async function bundleFunction(source, folder, options = {}) {
34500
36402
  result = await build({
34501
36403
  absWorkingDir: folder.root,
34502
36404
  entryPoints: [source.entry],
34503
- outfile: join31(source.dir, NARO_USER_MODULE),
36405
+ outfile: join30(source.dir, NARO_USER_MODULE),
34504
36406
  bundle: true,
34505
36407
  write: false,
34506
36408
  format: "esm",
@@ -35070,7 +36972,7 @@ function slugify2(value) {
35070
36972
  async function projectName(sourceDir) {
35071
36973
  try {
35072
36974
  const pkg = JSON.parse(
35073
- await readFile15(join32(sourceDir, "package.json"), "utf8")
36975
+ await readFile15(join31(sourceDir, "package.json"), "utf8")
35074
36976
  );
35075
36977
  if (pkg.name) return pkg.name;
35076
36978
  } catch {
@@ -35080,7 +36982,7 @@ async function projectName(sourceDir) {
35080
36982
  function resolveBin(name) {
35081
36983
  let dir = dirname10(fileURLToPath4(import.meta.url));
35082
36984
  for (; ; ) {
35083
- const candidate = join32(dir, "node_modules", ".bin", name);
36985
+ const candidate = join31(dir, "node_modules", ".bin", name);
35084
36986
  if (existsSync10(candidate)) return candidate;
35085
36987
  const parent = dirname10(dir);
35086
36988
  if (parent === dir) return name;
@@ -35147,7 +37049,7 @@ function workingTreeNotices(copy) {
35147
37049
  }
35148
37050
  function localDependencies(sourceDir, say = () => void 0) {
35149
37051
  return {
35150
- createWorkspace: () => mkdtemp2(join32(tmpdir2(), "naro-build-")),
37052
+ createWorkspace: () => mkdtemp2(join31(tmpdir2(), "naro-build-")),
35151
37053
  removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
35152
37054
  async runCommand(spec, observer) {
35153
37055
  if (spec.command === "git" && spec.args[0] === "clone") {
@@ -35402,6 +37304,8 @@ function deploymentUrl(detail, ctx, production) {
35402
37304
  }
35403
37305
 
35404
37306
  export {
37307
+ CAPABILITIES,
37308
+ dashboardNeeds,
35405
37309
  parseDotenvDetailed,
35406
37310
  FUNCTION_INVOKE_METHODS,
35407
37311
  FUNCTION_INVOKE_MAX_RESPONSE_BYTES,
@@ -35439,6 +37343,8 @@ export {
35439
37343
  POLICY_RULES,
35440
37344
  POLICY_ACTIONS,
35441
37345
  NaroClient,
37346
+ dashboardCapability,
37347
+ dashboardLink,
35442
37348
  BOOKMARK_FORMAT_MESSAGE,
35443
37349
  isBookmark,
35444
37350
  RESTORE_OUTCOME_UNKNOWN_CODES,