naro-cli 0.25.0 → 0.27.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,1896 @@ 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: "team-backend-usage",
537
+ domain: "teams",
538
+ title: "\uD300 \uBC31\uC5D4\uB4DC \uC0AC\uC6A9\uB7C9\xB7\uC77C\uBCC4 \uCC28\uD2B8(\uD568\uC218 CPU\xB7DB\xB7\uC2A4\uD1A0\uB9AC\uC9C0\xB7\uB9AC\uC5BC\uD0C0\uC784)",
539
+ routes: [
540
+ "GET teams/[teamSlug]/backend-usage",
541
+ "GET teams/[teamSlug]/usage/series"
542
+ ],
543
+ dashboard: "/[teamSlug]/projects",
544
+ ui: { kind: "built" },
545
+ cli: {
546
+ kind: "planned",
547
+ note: "\uD300 \uAC1C\uC694 \uCE74\uB4DC\uB85C \uBA3C\uC800 \uB0C8\uB2E4. naro usage\uC5D0 \uBC31\uC5D4\uB4DC \uC904\uC744 \uB354\uD558\uB294 \uAC83\uC774 \uB2E4\uC74C\uC774\uB2E4."
548
+ },
549
+ mcp: {
550
+ kind: "planned",
551
+ note: "get_team_usage\uC5D0 \uBC31\uC5D4\uB4DC \uC0AC\uC6A9\uB7C9\uC744 \uB354\uD558\uB294 \uAC83\uC774 \uB2E4\uC74C\uC774\uB2E4."
552
+ }
553
+ },
554
+ {
555
+ id: "billing-view",
556
+ domain: "billing",
557
+ title: "\uD50C\uB79C\xB7\uAD6C\uB3C5 \uC870\uD68C",
558
+ routes: ["GET teams/[teamSlug]/billing"],
559
+ ui: { kind: "built" },
560
+ cli: { kind: "built", commands: ["naro billing"] },
561
+ mcp: { kind: "built", tools: ["get_team_billing"] }
562
+ },
563
+ {
564
+ id: "billing-checkout",
565
+ domain: "billing",
566
+ title: "\uCCB4\uD06C\uC544\uC6C3",
567
+ routes: ["POST teams/[teamSlug]/billing/checkout"],
568
+ dashboard: "/[teamSlug]/usage",
569
+ ui: { kind: "built" },
570
+ cli: {
571
+ kind: "excluded",
572
+ reason: "payment-flow",
573
+ note: "\uCE74\uB4DC \uC815\uBCF4\uB294 Paddle\uC758 \uD638\uC2A4\uD305 \uACB0\uC81C\uCC3D\uC774 \uBC1B\uB294\uB2E4."
574
+ },
575
+ mcp: {
576
+ kind: "excluded",
577
+ reason: "payment-flow",
578
+ 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)."
579
+ }
580
+ },
581
+ {
582
+ id: "billing-portal",
583
+ domain: "billing",
584
+ title: "\uACB0\uC81C \uD3EC\uD138",
585
+ routes: ["POST teams/[teamSlug]/billing/portal"],
586
+ dashboard: "/[teamSlug]/usage",
587
+ ui: { kind: "built" },
588
+ cli: {
589
+ kind: "excluded",
590
+ reason: "payment-flow",
591
+ note: "\uACB0\uC81C \uC218\uB2E8 \uBCC0\uACBD\uC740 Paddle \uD3EC\uD138\uC5D0\uC11C \uD55C\uB2E4."
592
+ },
593
+ mcp: {
594
+ kind: "excluded",
595
+ reason: "payment-flow",
596
+ note: "\uACB0\uC81C \uC218\uB2E8 \uBCC0\uACBD\uC740 Paddle \uD3EC\uD138\uC5D0\uC11C \uD55C\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
597
+ }
598
+ },
599
+ {
600
+ id: "project-list",
601
+ domain: "projects",
602
+ title: "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D",
603
+ routes: ["GET projects"],
604
+ ui: { kind: "built" },
605
+ cli: { kind: "built", commands: ["naro projects"] },
606
+ mcp: { kind: "built", tools: ["list_projects"] }
607
+ },
608
+ {
609
+ id: "project-create",
610
+ domain: "projects",
611
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC0DD\uC131",
612
+ routes: ["POST projects"],
613
+ ui: { kind: "built" },
614
+ // naro deploy가 링크가 없을 때 만든다(POST cli/deployments).
615
+ cli: { kind: "built" },
616
+ // deploy가 링크가 없을 때 만든다(POST cli/deployments).
617
+ mcp: { kind: "built" }
618
+ },
619
+ {
620
+ id: "project-get",
621
+ domain: "projects",
622
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC870\uD68C",
623
+ routes: ["GET projects/[projectId]"],
624
+ ui: { kind: "built" },
625
+ cli: { kind: "built", commands: ["naro project inspect"] },
626
+ mcp: { kind: "built", tools: ["get_project"] }
627
+ },
628
+ {
629
+ id: "project-link",
630
+ domain: "projects",
631
+ title: "\uB514\uB809\uD130\uB9AC\uB97C \uD504\uB85C\uC81D\uD2B8\uC5D0 \uB9C1\uD06C",
632
+ ui: {
633
+ kind: "excluded",
634
+ reason: "local-filesystem",
635
+ note: ".naro/project.json\uC5D0 \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
636
+ },
637
+ cli: { kind: "built", commands: ["naro link", "naro init"] },
638
+ mcp: { kind: "built", tools: ["link_project"] }
639
+ },
640
+ {
641
+ id: "project-unlink",
642
+ domain: "projects",
643
+ title: "\uB9C1\uD06C \uD574\uC81C",
644
+ ui: {
645
+ kind: "excluded",
646
+ reason: "local-filesystem",
647
+ note: ".naro/\uB97C \uC9C0\uC6B4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
648
+ },
649
+ cli: { kind: "built", commands: ["naro unlink"] },
650
+ mcp: {
651
+ kind: "excluded",
652
+ reason: "agent-native",
653
+ 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."
654
+ }
655
+ },
656
+ {
657
+ id: "project-update",
658
+ domain: "projects",
659
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815 \uBCC0\uACBD(\uD504\uB808\uC784\uC6CC\uD06C\xB7\uC11C\uBE0C\uB3C4\uBA54\uC778)",
660
+ routes: ["PATCH projects/[projectId]"],
661
+ ui: { kind: "built" },
662
+ cli: { kind: "built", commands: ["naro project set"] },
663
+ // 서브도메인은 빼고(PR #433 리뷰): 옛 이름이 바로 비어 누구나 가져갈 수 있어
664
+ // 북마크·OAuth 리다이렉트가 남의 앱으로 간다. 대시보드와 CLI(먼저 묻는다)만.
665
+ mcp: { kind: "built", tools: ["update_project"] }
666
+ },
667
+ {
668
+ id: "project-delete",
669
+ domain: "projects",
670
+ title: "\uD504\uB85C\uC81D\uD2B8 \uC0AD\uC81C",
671
+ routes: ["DELETE projects/[projectId]"],
672
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
673
+ ui: { kind: "built" },
674
+ cli: { kind: "built", commands: ["naro project delete"] },
675
+ mcp: {
676
+ kind: "excluded",
677
+ reason: "irreversible",
678
+ 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)."
679
+ }
680
+ },
681
+ {
682
+ id: "project-subdomain-check",
683
+ domain: "projects",
684
+ title: "\uC11C\uBE0C\uB3C4\uBA54\uC778 \uC0AC\uC6A9 \uAC00\uB2A5 \uC5EC\uBD80",
685
+ routes: ["GET subdomains/availability"],
686
+ ui: { kind: "built" },
687
+ cli: { kind: "built", commands: ["naro project check"] },
688
+ mcp: { kind: "built", tools: ["check_subdomain"] }
689
+ },
690
+ {
691
+ id: "project-access-list",
692
+ domain: "projects",
693
+ title: "\uBA64\uBC84\uBCC4 \uD504\uB85C\uC81D\uD2B8 \uC811\uADFC \uC870\uD68C",
694
+ routes: ["GET projects/[projectId]/access"],
695
+ ui: { kind: "built" },
696
+ cli: { kind: "built", commands: ["naro project access"] },
697
+ mcp: { kind: "built", tools: ["list_project_access"] }
698
+ },
699
+ {
700
+ id: "project-access-grant",
701
+ domain: "projects",
702
+ title: "\uBA64\uBC84\uC5D0\uAC8C \uD504\uB85C\uC81D\uD2B8 \uC811\uADFC \uBD80\uC5EC",
703
+ routes: ["PUT projects/[projectId]/access"],
704
+ dashboard: "/[teamSlug]/projects/[projectSlug]/access",
705
+ ui: { kind: "built" },
706
+ cli: { kind: "built", commands: ["naro project access grant"] },
707
+ mcp: {
708
+ kind: "excluded",
709
+ reason: "privilege-escalation",
710
+ note: "\uBA64\uBC84\uC758 \uD504\uB85C\uC81D\uD2B8 \uAD8C\uD55C\uC744 \uBC14\uAFBC\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
711
+ }
712
+ },
713
+ {
714
+ id: "project-access-revoke",
715
+ domain: "projects",
716
+ title: "\uBA64\uBC84\uC758 \uD504\uB85C\uC81D\uD2B8 \uC811\uADFC \uD68C\uC218",
717
+ routes: ["DELETE projects/[projectId]/access"],
718
+ dashboard: "/[teamSlug]/projects/[projectSlug]/access",
719
+ ui: { kind: "built" },
720
+ cli: { kind: "built", commands: ["naro project access revoke"] },
721
+ mcp: {
722
+ kind: "excluded",
723
+ reason: "privilege-escalation",
724
+ note: "\uBA64\uBC84\uC758 \uD504\uB85C\uC81D\uD2B8 \uAD8C\uD55C\uC744 \uBC14\uAFBC\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
725
+ }
726
+ },
727
+ {
728
+ id: "deployment-list",
729
+ domain: "deployments",
730
+ title: "\uBC30\uD3EC \uBAA9\uB85D",
731
+ routes: ["GET projects/[projectId]/deployments"],
732
+ ui: { kind: "built" },
733
+ cli: { kind: "built", commands: ["naro deployments"] },
734
+ mcp: { kind: "built", tools: ["list_deployments"] }
735
+ },
736
+ {
737
+ id: "deployment-create",
738
+ domain: "deployments",
739
+ title: "\uBE0C\uB79C\uCE58\uC5D0\uC11C \uC0C8\uB85C \uBC30\uD3EC",
740
+ routes: ["POST projects/[projectId]/deployments"],
741
+ ui: { kind: "built" },
742
+ cli: { kind: "built", commands: ["naro deployments create"] },
743
+ mcp: { kind: "built", tools: ["deploy_from_git"] }
744
+ },
745
+ {
746
+ id: "deployment-local",
747
+ domain: "deployments",
748
+ title: "\uB85C\uCEEC\uC5D0\uC11C \uBE4C\uB4DC\uD574 \uBC30\uD3EC",
749
+ routes: ["POST cli/deployments"],
750
+ ui: {
751
+ kind: "excluded",
752
+ reason: "local-filesystem",
753
+ 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."
754
+ },
755
+ cli: { kind: "built", commands: ["naro deploy"] },
756
+ mcp: { kind: "built", tools: ["deploy"] }
757
+ },
758
+ {
759
+ id: "deployment-get",
760
+ domain: "deployments",
761
+ title: "\uBC30\uD3EC \uC0C1\uC138",
762
+ routes: ["GET deployments/[deploymentId]"],
763
+ ui: { kind: "built" },
764
+ cli: { kind: "built", commands: ["naro inspect"] },
765
+ mcp: { kind: "built", tools: ["get_deployment", "get_build_logs"] }
766
+ },
767
+ {
768
+ id: "deployment-cancel",
769
+ domain: "deployments",
770
+ title: "\uBE4C\uB4DC \uCDE8\uC18C",
771
+ routes: ["POST projects/[projectId]/deployments/[deploymentId]/cancel"],
772
+ ui: { kind: "built" },
773
+ cli: { kind: "built", commands: ["naro cancel"] },
774
+ mcp: { kind: "built", tools: ["cancel_deployment"] }
775
+ },
776
+ {
777
+ id: "deployment-promote-preview",
778
+ domain: "deployments",
779
+ title: "\uC2B9\uACA9 \uBBF8\uB9AC\uBCF4\uAE30",
780
+ routes: ["GET projects/[projectId]/deployments/[deploymentId]/promote"],
781
+ ui: { kind: "built" },
782
+ // naro promote가 묻기 전에 무엇을 할지 보여 준다(client.deployments.promoteCheck).
783
+ cli: { kind: "built" },
784
+ mcp: { kind: "built", tools: ["check_promotion"] }
785
+ },
786
+ {
787
+ id: "deployment-promote",
788
+ domain: "deployments",
789
+ title: "\uD504\uB85C\uB355\uC158\xB7\uD658\uACBD\uC73C\uB85C \uC2B9\uACA9(\uB864\uBC31 \uD3EC\uD568)",
790
+ routes: ["POST projects/[projectId]/deployments/[deploymentId]/promote"],
791
+ ui: { kind: "built" },
792
+ cli: { kind: "built", commands: ["naro promote", "naro rollback"] },
793
+ mcp: {
794
+ kind: "built",
795
+ tools: ["promote_deployment", "rollback_deployment"]
796
+ }
797
+ },
798
+ {
799
+ id: "git-app-install",
800
+ domain: "git",
801
+ title: "GitHub App \uC124\uCE58",
802
+ routes: ["GET github/authorize", "GET github/install", "GET github/setup"],
803
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
804
+ ui: { kind: "built" },
805
+ cli: {
806
+ kind: "excluded",
807
+ reason: "browser-required",
808
+ 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."
809
+ },
810
+ mcp: {
811
+ kind: "excluded",
812
+ reason: "browser-required",
813
+ 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."
814
+ }
815
+ },
816
+ {
817
+ id: "git-repository-list",
818
+ domain: "git",
819
+ title: "GitHub \uC800\uC7A5\uC18C \uBAA9\uB85D",
820
+ routes: ["GET github/repositories"],
821
+ ui: { kind: "built" },
822
+ // naro git connect가 저장소를 고를 때 읽는다(client.github.listRepositories).
823
+ cli: { kind: "built" },
824
+ mcp: { kind: "built", tools: ["list_git_repositories"] }
825
+ },
826
+ {
827
+ id: "git-connect",
828
+ domain: "git",
829
+ title: "\uC800\uC7A5\uC18C \uC5F0\uACB0",
830
+ routes: ["POST projects/[projectId]/git"],
831
+ ui: { kind: "built" },
832
+ cli: { kind: "built", commands: ["naro git connect"] },
833
+ mcp: { kind: "built", tools: ["connect_git_repository"] }
834
+ },
835
+ {
836
+ id: "git-disconnect",
837
+ domain: "git",
838
+ title: "\uC800\uC7A5\uC18C \uC5F0\uACB0 \uD574\uC81C",
839
+ routes: ["DELETE projects/[projectId]/git"],
840
+ ui: { kind: "built" },
841
+ cli: { kind: "built", commands: ["naro git disconnect"] },
842
+ mcp: { kind: "built", tools: ["disconnect_git_repository"] }
843
+ },
844
+ {
845
+ id: "env-list",
846
+ domain: "env",
847
+ title: "\uD658\uACBD\uBCC0\uC218 \uBAA9\uB85D",
848
+ routes: ["GET projects/[projectId]/env"],
849
+ ui: { kind: "built" },
850
+ cli: { kind: "built", commands: ["naro env list"] },
851
+ mcp: { kind: "built", tools: ["list_environment_variables"] }
852
+ },
853
+ {
854
+ id: "env-set",
855
+ domain: "env",
856
+ title: "\uD658\uACBD\uBCC0\uC218 \uCD94\uAC00\xB7\uC218\uC815",
857
+ routes: ["POST projects/[projectId]/env"],
858
+ ui: { kind: "built" },
859
+ cli: { kind: "built", commands: ["naro env add", "naro env import"] },
860
+ mcp: { kind: "built", tools: ["set_environment_variable"] }
861
+ },
862
+ {
863
+ id: "env-remove",
864
+ domain: "env",
865
+ title: "\uD658\uACBD\uBCC0\uC218 \uC81C\uAC70",
866
+ routes: ["DELETE projects/[projectId]/env/[envVarId]"],
867
+ ui: { kind: "built" },
868
+ cli: { kind: "built", commands: ["naro env remove"] },
869
+ mcp: { kind: "built", tools: ["remove_environment_variable"] }
870
+ },
871
+ // 앱 메일의 Resend 연결(spec 2026-11-04 resend-email-integration). 연결은
872
+ // env 두 행이라 env 도메인에 둔다. CLI·MCP는 그 스펙의 PR 2다.
873
+ {
874
+ id: "email-status",
875
+ domain: "env",
876
+ title: "Resend \uC5F0\uACB0 \uBCF4\uAE30",
877
+ routes: ["GET projects/[projectId]/email"],
878
+ ui: { kind: "built" },
879
+ cli: { kind: "planned", note: "naro email status \u2014 \uC2A4\uD399 \xA713 PR 2." },
880
+ mcp: { kind: "planned", note: "get_email \u2014 \uC2A4\uD399 \xA713 PR 2." }
881
+ },
882
+ {
883
+ id: "email-connect",
884
+ domain: "env",
885
+ title: "Resend \uC5F0\uACB0\xB7\uBC14\uAFB8\uAE30",
886
+ routes: ["PUT projects/[projectId]/email"],
887
+ dashboard: "/[teamSlug]/projects/[projectSlug]/email",
888
+ ui: { kind: "built" },
889
+ cli: {
890
+ kind: "planned",
891
+ note: "naro email connect --api-key-stdin \u2014 \uC2A4\uD399 \xA713 PR 2."
892
+ },
893
+ mcp: {
894
+ kind: "excluded",
895
+ reason: "secret-value",
896
+ 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)."
897
+ }
898
+ },
899
+ {
900
+ id: "email-disconnect",
901
+ domain: "env",
902
+ title: "Resend \uC5F0\uACB0 \uB04A\uAE30",
903
+ routes: ["DELETE projects/[projectId]/email"],
904
+ ui: { kind: "built" },
905
+ cli: { kind: "planned", note: "naro email disconnect \u2014 \uC2A4\uD399 \xA713 PR 2." },
906
+ mcp: { kind: "planned", note: "disconnect_email \u2014 \uC2A4\uD399 \xA713 PR 2." }
907
+ },
908
+ {
909
+ id: "env-pull",
910
+ domain: "env",
911
+ title: "\uD658\uACBD\uBCC0\uC218\uB97C \uB85C\uCEEC\uB85C \uB0B4\uB9AC\uAE30",
912
+ ui: {
913
+ kind: "excluded",
914
+ reason: "local-filesystem",
915
+ note: ".env.local\uC5D0 \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
916
+ },
917
+ cli: { kind: "built", commands: ["naro env pull"] },
918
+ mcp: {
919
+ kind: "excluded",
920
+ reason: "secret-value",
921
+ 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."
922
+ }
923
+ },
924
+ {
925
+ id: "environment-list",
926
+ domain: "environments",
927
+ title: "\uD658\uACBD \uBAA9\uB85D",
928
+ routes: ["GET projects/[projectId]/environments"],
929
+ ui: { kind: "built" },
930
+ cli: { kind: "built", commands: ["naro environments"] },
931
+ mcp: { kind: "built", tools: ["list_environments"] }
932
+ },
933
+ {
934
+ id: "environment-create",
935
+ domain: "environments",
936
+ title: "\uD658\uACBD \uB9CC\uB4E4\uAE30",
937
+ routes: ["POST projects/[projectId]/environments"],
938
+ ui: { kind: "built" },
939
+ cli: { kind: "built", commands: ["naro environments create"] },
940
+ mcp: { kind: "built", tools: ["create_environment"] }
941
+ },
942
+ {
943
+ id: "environment-update",
944
+ domain: "environments",
945
+ title: "\uD658\uACBD \uC124\uC815 \uBCC0\uACBD(\uBE0C\uB79C\uCE58\xB7\uBCF4\uD638)",
946
+ routes: ["PATCH projects/[projectId]/environments/[environmentId]"],
947
+ ui: { kind: "built" },
948
+ cli: { kind: "built", commands: ["naro environments set"] },
949
+ mcp: { kind: "built", tools: ["update_environment"] }
950
+ },
951
+ {
952
+ id: "environment-delete",
953
+ domain: "environments",
954
+ title: "\uD658\uACBD \uC0AD\uC81C",
955
+ routes: ["DELETE projects/[projectId]/environments/[environmentId]"],
956
+ ui: { kind: "built" },
957
+ cli: { kind: "built", commands: ["naro environments delete"] },
958
+ mcp: { kind: "built", tools: ["delete_environment"] }
959
+ },
960
+ {
961
+ id: "environment-provision",
962
+ domain: "environments",
963
+ title: "\uD658\uACBD \uB370\uC774\uD130 \uD504\uB85C\uBE44\uC800\uB2DD",
964
+ routes: [
965
+ "POST projects/[projectId]/environments/[environmentId]/provision"
966
+ ],
967
+ ui: { kind: "built" },
968
+ cli: { kind: "built", commands: ["naro environments provision"] },
969
+ mcp: { kind: "built", tools: ["provision_environment"] }
970
+ },
971
+ {
972
+ id: "domain-list",
973
+ domain: "domains",
974
+ title: "\uB3C4\uBA54\uC778 \uBAA9\uB85D",
975
+ routes: ["GET projects/[projectId]/domains"],
976
+ ui: { kind: "built" },
977
+ cli: { kind: "built", commands: ["naro domains list"] },
978
+ mcp: { kind: "built", tools: ["list_domains"] }
979
+ },
980
+ {
981
+ id: "domain-add",
982
+ domain: "domains",
983
+ title: "\uB3C4\uBA54\uC778 \uCD94\uAC00",
984
+ routes: ["POST projects/[projectId]/domains"],
985
+ ui: { kind: "built" },
986
+ cli: { kind: "built", commands: ["naro domains add"] },
987
+ mcp: { kind: "built", tools: ["add_domain"] }
988
+ },
989
+ {
990
+ id: "domain-refresh",
991
+ domain: "domains",
992
+ title: "\uB3C4\uBA54\uC778 \uC0C1\uD0DC \uC0C8\uB85C\uACE0\uCE68",
993
+ routes: ["PATCH projects/[projectId]/domains/[domainId]"],
994
+ ui: { kind: "built" },
995
+ // naro domains verify가 Cloudflare 재확인까지 해서 refresh(저장된 상태 다시 읽기)의
996
+ // 상위다. 같은 PATCH는 부르지 않는다.
997
+ cli: { kind: "built" },
998
+ // verify_domain이 같은 일을 더 한다(재확인 + DNS 점검).
999
+ mcp: { kind: "built" }
1000
+ },
1001
+ {
1002
+ id: "domain-remove",
1003
+ domain: "domains",
1004
+ title: "\uB3C4\uBA54\uC778 \uC81C\uAC70",
1005
+ routes: ["DELETE projects/[projectId]/domains/[domainId]"],
1006
+ ui: { kind: "built" },
1007
+ cli: { kind: "built", commands: ["naro domains remove"] },
1008
+ mcp: { kind: "built", tools: ["remove_domain"] }
1009
+ },
1010
+ {
1011
+ id: "domain-verify",
1012
+ domain: "domains",
1013
+ title: "DNS \uC7AC\uAC80\uC99D",
1014
+ routes: ["POST projects/[projectId]/domains/[domainId]/verify"],
1015
+ ui: { kind: "built" },
1016
+ cli: { kind: "built", commands: ["naro domains verify"] },
1017
+ mcp: { kind: "built", tools: ["verify_domain"] }
1018
+ },
1019
+ {
1020
+ id: "domain-claim-list",
1021
+ domain: "domains",
1022
+ title: "\uB2E4\uB978 \uD504\uB85C\uC81D\uD2B8\uAC00 \uAC00\uC9C4 \uB3C4\uBA54\uC778\uC758 \uC18C\uC720\uAD8C \uD655\uC778 \uBAA9\uB85D",
1023
+ routes: ["GET projects/[projectId]/domain-claims"],
1024
+ ui: { kind: "built" },
1025
+ // naro domains verify가 소유권 확인을 함께 읽는다(client.domains.listClaims).
1026
+ cli: { kind: "built" },
1027
+ // verify_domain이 소유권 확인을 함께 읽는다(client.domains.listClaims).
1028
+ mcp: { kind: "built" }
1029
+ },
1030
+ {
1031
+ id: "domain-claim-verify",
1032
+ domain: "domains",
1033
+ title: "\uB3C4\uBA54\uC778 \uC18C\uC720\uAD8C \uD655\uC778",
1034
+ routes: ["POST projects/[projectId]/domain-claims/[claimId]/verify"],
1035
+ ui: { kind: "built" },
1036
+ // naro domains verify가 소유권 확인을 함께 다시 본다(client.domains.verifyClaim).
1037
+ cli: { kind: "built" },
1038
+ // verify_domain이 소유권 확인을 함께 다시 본다(client.domains.verifyClaim).
1039
+ mcp: { kind: "built" }
1040
+ },
1041
+ {
1042
+ id: "domain-claim-cancel",
1043
+ domain: "domains",
1044
+ title: "\uB3C4\uBA54\uC778 \uC18C\uC720\uAD8C \uD655\uC778 \uCDE8\uC18C",
1045
+ routes: ["DELETE projects/[projectId]/domain-claims/[claimId]"],
1046
+ ui: { kind: "built" },
1047
+ // naro domains remove가 붙어 있지 않은 호스트의 소유권 확인을 취소한다.
1048
+ cli: { kind: "built" },
1049
+ // remove_domain이 붙어 있지 않은 호스트의 소유권 확인을 취소한다(claimCancelled).
1050
+ mcp: { kind: "built" }
1051
+ },
1052
+ {
1053
+ id: "dns-zone-list",
1054
+ domain: "dns",
1055
+ title: "DNS \uC874 \uBAA9\uB85D",
1056
+ routes: ["GET teams/[teamSlug]/dns-zones"],
1057
+ ui: { kind: "built" },
1058
+ cli: { kind: "built", commands: ["naro dns"] },
1059
+ mcp: { kind: "built", tools: ["get_dns"] }
1060
+ },
1061
+ {
1062
+ id: "dns-zone-add",
1063
+ domain: "dns",
1064
+ title: "DNS \uC874 \uCD94\uAC00",
1065
+ routes: ["POST teams/[teamSlug]/dns-zones"],
1066
+ ui: { kind: "built" },
1067
+ cli: { kind: "built", commands: ["naro dns add"] },
1068
+ mcp: { kind: "built", tools: ["add_dns_zone"] }
1069
+ },
1070
+ {
1071
+ id: "dns-zone-remove",
1072
+ domain: "dns",
1073
+ title: "DNS \uC874 \uC0AD\uC81C",
1074
+ routes: ["DELETE teams/[teamSlug]/dns-zones/[zoneId]"],
1075
+ ui: { kind: "built" },
1076
+ cli: { kind: "built", commands: ["naro dns remove"] },
1077
+ mcp: { kind: "built", tools: ["remove_dns_zone"] }
1078
+ },
1079
+ {
1080
+ id: "dns-zone-check",
1081
+ domain: "dns",
1082
+ title: "\uB124\uC784\uC11C\uBC84 \uC774\uAD00 \uD655\uC778",
1083
+ routes: ["POST teams/[teamSlug]/dns-zones/[zoneId]/check"],
1084
+ ui: { kind: "built" },
1085
+ cli: { kind: "built", commands: ["naro dns check"] },
1086
+ mcp: { kind: "built", tools: ["check_dns_zone"] }
1087
+ },
1088
+ {
1089
+ id: "dns-zone-precheck",
1090
+ domain: "dns",
1091
+ title: "\uC774\uAD00 \uC804 \uBA54\uC77C \uD655\uC778",
1092
+ routes: ["GET teams/[teamSlug]/dns-zones/[zoneId]/precheck"],
1093
+ ui: { kind: "built" },
1094
+ cli: { kind: "built", commands: ["naro dns mail"] },
1095
+ mcp: { kind: "built", tools: ["check_dns_mail"] }
1096
+ },
1097
+ {
1098
+ id: "dns-zone-scan",
1099
+ domain: "dns",
1100
+ title: "\uAE30\uC874 DNS \uB808\uCF54\uB4DC \uC2A4\uCE94 \uACB0\uACFC",
1101
+ routes: ["GET teams/[teamSlug]/dns-zones/[zoneId]/scan"],
1102
+ ui: { kind: "built" },
1103
+ cli: { kind: "built", commands: ["naro dns scan"] },
1104
+ mcp: { kind: "built", tools: ["get_dns_scan"] }
1105
+ },
1106
+ {
1107
+ id: "dns-zone-scan-import",
1108
+ domain: "dns",
1109
+ title: "\uC2A4\uCE94\uD55C \uB808\uCF54\uB4DC \uC77C\uAD04 \uAC00\uC838\uC624\uAE30",
1110
+ routes: ["POST teams/[teamSlug]/dns-zones/[zoneId]/scan"],
1111
+ ui: { kind: "built" },
1112
+ cli: { kind: "built", commands: ["naro dns import"] },
1113
+ mcp: { kind: "built", tools: ["import_dns_scan"] }
1114
+ },
1115
+ {
1116
+ id: "dns-record-list",
1117
+ domain: "dns",
1118
+ title: "DNS \uB808\uCF54\uB4DC \uBAA9\uB85D",
1119
+ routes: ["GET teams/[teamSlug]/dns-zones/[zoneId]/records"],
1120
+ ui: { kind: "built" },
1121
+ cli: { kind: "built", commands: ["naro dns records"] },
1122
+ mcp: { kind: "built", tools: ["list_dns_records"] }
1123
+ },
1124
+ {
1125
+ id: "dns-record-add",
1126
+ domain: "dns",
1127
+ title: "DNS \uB808\uCF54\uB4DC \uCD94\uAC00",
1128
+ routes: ["POST teams/[teamSlug]/dns-zones/[zoneId]/records"],
1129
+ ui: { kind: "built" },
1130
+ cli: { kind: "built", commands: ["naro dns records add"] },
1131
+ mcp: { kind: "built", tools: ["add_dns_record"] }
1132
+ },
1133
+ {
1134
+ id: "dns-record-update",
1135
+ domain: "dns",
1136
+ title: "DNS \uB808\uCF54\uB4DC \uC218\uC815",
1137
+ routes: ["PATCH teams/[teamSlug]/dns-zones/[zoneId]/records/[recordId]"],
1138
+ ui: { kind: "built" },
1139
+ cli: { kind: "built", commands: ["naro dns records update"] },
1140
+ mcp: { kind: "built", tools: ["update_dns_record"] }
1141
+ },
1142
+ {
1143
+ id: "dns-record-remove",
1144
+ domain: "dns",
1145
+ title: "DNS \uB808\uCF54\uB4DC \uC0AD\uC81C",
1146
+ routes: ["DELETE teams/[teamSlug]/dns-zones/[zoneId]/records/[recordId]"],
1147
+ ui: { kind: "built" },
1148
+ cli: { kind: "built", commands: ["naro dns records remove"] },
1149
+ mcp: { kind: "built", tools: ["remove_dns_record"] }
1150
+ },
1151
+ {
1152
+ id: "protection-get",
1153
+ domain: "protection",
1154
+ title: "\uD504\uB9AC\uBDF0 \uBCF4\uD638 \uC0C1\uD0DC",
1155
+ routes: ["GET projects/[projectId]/preview-protection"],
1156
+ ui: { kind: "built" },
1157
+ cli: { kind: "built", commands: ["naro protection"] },
1158
+ mcp: { kind: "built", tools: ["get_preview_protection"] }
1159
+ },
1160
+ {
1161
+ id: "protection-set",
1162
+ domain: "protection",
1163
+ title: "\uD504\uB9AC\uBDF0 \uBCF4\uD638 \uCF1C\uAE30\xB7\uB044\uAE30",
1164
+ routes: ["PUT projects/[projectId]/preview-protection"],
1165
+ ui: { kind: "built" },
1166
+ cli: {
1167
+ kind: "built",
1168
+ commands: ["naro protection enable", "naro protection disable"]
1169
+ },
1170
+ mcp: { kind: "built", tools: ["set_preview_protection"] }
1171
+ },
1172
+ {
1173
+ id: "protection-bypass-create",
1174
+ domain: "protection",
1175
+ title: "\uC6B0\uD68C \uC2DC\uD06C\uB9BF \uB9CC\uB4E4\uAE30",
1176
+ routes: ["POST projects/[projectId]/preview-protection/bypass"],
1177
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1178
+ ui: { kind: "built" },
1179
+ cli: { kind: "built", commands: ["naro protection bypass"] },
1180
+ mcp: {
1181
+ kind: "excluded",
1182
+ reason: "secret-value",
1183
+ 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."
1184
+ }
1185
+ },
1186
+ {
1187
+ id: "protection-bypass-remove",
1188
+ domain: "protection",
1189
+ title: "\uC6B0\uD68C \uC2DC\uD06C\uB9BF \uC81C\uAC70",
1190
+ routes: ["DELETE projects/[projectId]/preview-protection/bypass"],
1191
+ ui: { kind: "built" },
1192
+ cli: { kind: "built", commands: ["naro protection bypass remove"] },
1193
+ mcp: { kind: "built", tools: ["remove_preview_protection_bypass"] }
1194
+ },
1195
+ {
1196
+ id: "database-info",
1197
+ domain: "database",
1198
+ title: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC0C1\uD0DC\xB7\uC6A9\uB7C9\xB7\uD55C\uB3C4",
1199
+ routes: ["GET projects/[projectId]/database"],
1200
+ ui: { kind: "built" },
1201
+ cli: { kind: "built", commands: ["naro db info"] },
1202
+ mcp: { kind: "built", tools: ["get_database"] }
1203
+ },
1204
+ {
1205
+ id: "database-enable",
1206
+ domain: "database",
1207
+ title: "\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uB9CC\uB4E4\uAE30",
1208
+ routes: ["POST projects/[projectId]/database"],
1209
+ ui: { kind: "built" },
1210
+ cli: { kind: "built", commands: ["naro db enable"] },
1211
+ mcp: { kind: "built", tools: ["enable_database"] }
1212
+ },
1213
+ {
1214
+ id: "database-query",
1215
+ domain: "database",
1216
+ title: "SQL \uC2E4\uD589",
1217
+ routes: ["POST projects/[projectId]/database/query"],
1218
+ ui: { kind: "built" },
1219
+ cli: { kind: "built", commands: ["naro db query"] },
1220
+ mcp: { kind: "built", tools: ["execute_sql"] }
1221
+ },
1222
+ {
1223
+ id: "database-tables",
1224
+ domain: "database",
1225
+ title: "\uD14C\uC774\uBE14 \uBAA9\uB85D",
1226
+ routes: ["GET projects/[projectId]/database/tables"],
1227
+ ui: { kind: "built" },
1228
+ // naro db 명령들이 테이블 목록을 읽는다(client.db.tables).
1229
+ cli: { kind: "built" },
1230
+ mcp: { kind: "built", tools: ["list_tables"] }
1231
+ },
1232
+ {
1233
+ id: "database-schema-change",
1234
+ domain: "database",
1235
+ title: "\uD14C\uC774\uBE14\xB7\uC778\uB371\uC2A4 \uBC14\uAFB8\uAE30",
1236
+ routes: ["POST projects/[projectId]/database/schema"],
1237
+ ui: { kind: "built" },
1238
+ cli: {
1239
+ kind: "built",
1240
+ commands: [
1241
+ "naro db table create",
1242
+ "naro db table add-column",
1243
+ "naro db table rename-column",
1244
+ "naro db table rename",
1245
+ "naro db table drop-column",
1246
+ "naro db table drop",
1247
+ "naro db index create",
1248
+ "naro db index drop"
1249
+ ]
1250
+ },
1251
+ mcp: {
1252
+ kind: "built",
1253
+ tools: [
1254
+ "create_table",
1255
+ "alter_table",
1256
+ "drop_table",
1257
+ "create_index",
1258
+ "drop_index"
1259
+ ]
1260
+ }
1261
+ },
1262
+ {
1263
+ id: "database-migration-list",
1264
+ domain: "database",
1265
+ title: "\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uBAA9\uB85D",
1266
+ routes: ["GET projects/[projectId]/database/migrations"],
1267
+ ui: { kind: "built" },
1268
+ cli: { kind: "built", commands: ["naro migration list"] },
1269
+ mcp: { kind: "built", tools: ["list_migrations"] }
1270
+ },
1271
+ {
1272
+ id: "database-migration-apply",
1273
+ domain: "database",
1274
+ title: "\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC801\uC6A9",
1275
+ routes: ["POST projects/[projectId]/database/migrations"],
1276
+ ui: { kind: "built" },
1277
+ cli: { kind: "built", commands: ["naro db push", "naro migration up"] },
1278
+ mcp: { kind: "built", tools: ["apply_migration"] }
1279
+ },
1280
+ {
1281
+ id: "database-export",
1282
+ domain: "database",
1283
+ title: "\uB364\uD504 \uB0B4\uB824\uBC1B\uAE30",
1284
+ routes: ["POST projects/[projectId]/database/export"],
1285
+ ui: { kind: "built" },
1286
+ cli: { kind: "built", commands: ["naro db dump"] },
1287
+ mcp: { kind: "built", tools: ["export_database"] }
1288
+ },
1289
+ {
1290
+ id: "database-restore",
1291
+ domain: "database",
1292
+ title: "Time Travel \uBCF5\uAD6C",
1293
+ routes: ["POST projects/[projectId]/database/restore"],
1294
+ ui: { kind: "built" },
1295
+ cli: { kind: "built", commands: ["naro db restore"] },
1296
+ mcp: { kind: "built", tools: ["restore_database"] }
1297
+ },
1298
+ {
1299
+ id: "database-bookmark",
1300
+ domain: "database",
1301
+ title: "\uBCF5\uAD6C \uC9C0\uC810(\uBD81\uB9C8\uD06C) \uC870\uD68C",
1302
+ routes: ["GET projects/[projectId]/database/bookmark"],
1303
+ ui: { kind: "built" },
1304
+ // naro db restore가 되돌릴 북마크를 먼저 읽는다(client.db.bookmark).
1305
+ cli: { kind: "built" },
1306
+ // restore_database가 되돌릴 북마크를 먼저 읽는다(client.db.bookmark).
1307
+ mcp: { kind: "built" }
1308
+ },
1309
+ {
1310
+ id: "database-pull",
1311
+ domain: "database",
1312
+ title: "\uC6D0\uACA9 \uC2A4\uD0A4\uB9C8\uB97C \uBCA0\uC774\uC2A4\uB77C\uC778 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uC73C\uB85C",
1313
+ routes: ["POST projects/[projectId]/database/pull"],
1314
+ ui: {
1315
+ kind: "excluded",
1316
+ reason: "local-filesystem",
1317
+ 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."
1318
+ },
1319
+ cli: { kind: "built", commands: ["naro db pull"] },
1320
+ mcp: { kind: "built", tools: ["pull_database_schema"] }
1321
+ },
1322
+ {
1323
+ id: "database-reset",
1324
+ domain: "database",
1325
+ title: "\uC804\uCCB4 \uB9AC\uC14B \uD6C4 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uB2E4\uC2DC \uC801\uC6A9",
1326
+ ui: {
1327
+ kind: "excluded",
1328
+ reason: "local-filesystem",
1329
+ 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."
1330
+ },
1331
+ cli: { kind: "built", commands: ["naro db reset"] },
1332
+ mcp: {
1333
+ kind: "excluded",
1334
+ reason: "irreversible",
1335
+ 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)."
1336
+ }
1337
+ },
1338
+ {
1339
+ id: "database-lint",
1340
+ domain: "database",
1341
+ title: "\uC2A4\uD0A4\uB9C8\xB7RLS \uC815\uCC45 \uB9B0\uD2B8",
1342
+ ui: { kind: "built" },
1343
+ cli: { kind: "built", commands: ["naro db lint"] },
1344
+ mcp: { kind: "built", tools: ["lint_database"] }
1345
+ },
1346
+ {
1347
+ id: "database-gen-types",
1348
+ domain: "database",
1349
+ title: "\uC2A4\uD0A4\uB9C8\uC5D0\uC11C TypeScript \uD0C0\uC785 \uC0DD\uC131",
1350
+ ui: { kind: "built" },
1351
+ cli: { kind: "built", commands: ["naro gen types"] },
1352
+ mcp: { kind: "built", tools: ["generate_typescript_types"] }
1353
+ },
1354
+ {
1355
+ id: "database-migration-new",
1356
+ domain: "database",
1357
+ title: "\uC0C8 \uB9C8\uC774\uADF8\uB808\uC774\uC158 \uD30C\uC77C",
1358
+ ui: {
1359
+ kind: "excluded",
1360
+ reason: "local-filesystem",
1361
+ note: "./migrations\uC5D0 \uD30C\uC77C\uC744 \uB9CC\uB4E0\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
1362
+ },
1363
+ cli: { kind: "built", commands: ["naro migration new"] },
1364
+ mcp: {
1365
+ kind: "excluded",
1366
+ reason: "agent-native",
1367
+ 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."
1368
+ }
1369
+ },
1370
+ {
1371
+ id: "database-migration-repair",
1372
+ domain: "database",
1373
+ title: "\uB9C8\uC774\uADF8\uB808\uC774\uC158 \uC6D0\uC7A5 \uC218\uB9AC",
1374
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/migrations",
1375
+ ui: { kind: "built" },
1376
+ cli: { kind: "built", commands: ["naro migration repair"] },
1377
+ mcp: {
1378
+ kind: "excluded",
1379
+ reason: "irreversible",
1380
+ 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)."
1381
+ }
1382
+ },
1383
+ {
1384
+ id: "database-saved-query-list",
1385
+ domain: "database",
1386
+ title: "\uC800\uC7A5\uD55C \uCFFC\uB9AC \uBAA9\uB85D",
1387
+ routes: ["GET projects/[projectId]/database/saved-queries"],
1388
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1389
+ ui: { kind: "built" },
1390
+ cli: {
1391
+ kind: "excluded",
1392
+ reason: "ui-affordance",
1393
+ 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."
1394
+ },
1395
+ mcp: {
1396
+ kind: "excluded",
1397
+ reason: "ui-affordance",
1398
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1399
+ }
1400
+ },
1401
+ {
1402
+ id: "database-saved-query-create",
1403
+ domain: "database",
1404
+ title: "\uCFFC\uB9AC \uC800\uC7A5",
1405
+ routes: ["POST projects/[projectId]/database/saved-queries"],
1406
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1407
+ ui: { kind: "built" },
1408
+ cli: {
1409
+ kind: "excluded",
1410
+ reason: "ui-affordance",
1411
+ 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."
1412
+ },
1413
+ mcp: {
1414
+ kind: "excluded",
1415
+ reason: "ui-affordance",
1416
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1417
+ }
1418
+ },
1419
+ {
1420
+ id: "database-saved-query-update",
1421
+ domain: "database",
1422
+ title: "\uC800\uC7A5\uD55C \uCFFC\uB9AC \uC218\uC815",
1423
+ routes: [
1424
+ "PATCH projects/[projectId]/database/saved-queries/[savedQueryId]"
1425
+ ],
1426
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1427
+ ui: { kind: "built" },
1428
+ cli: {
1429
+ kind: "excluded",
1430
+ reason: "ui-affordance",
1431
+ 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."
1432
+ },
1433
+ mcp: {
1434
+ kind: "excluded",
1435
+ reason: "ui-affordance",
1436
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1437
+ }
1438
+ },
1439
+ {
1440
+ id: "database-saved-query-delete",
1441
+ domain: "database",
1442
+ title: "\uC800\uC7A5\uD55C \uCFFC\uB9AC \uC0AD\uC81C",
1443
+ routes: [
1444
+ "DELETE projects/[projectId]/database/saved-queries/[savedQueryId]"
1445
+ ],
1446
+ dashboard: "/[teamSlug]/projects/[projectSlug]/database/sql",
1447
+ ui: { kind: "built" },
1448
+ cli: {
1449
+ kind: "excluded",
1450
+ reason: "ui-affordance",
1451
+ 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."
1452
+ },
1453
+ mcp: {
1454
+ kind: "excluded",
1455
+ reason: "ui-affordance",
1456
+ note: "SQL \uD3B8\uC9D1\uAE30 \uC548\uC5D0\uC11C\uB9CC \uB73B\uC774 \uC788\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1457
+ }
1458
+ },
1459
+ {
1460
+ // 대시보드 정책 화면(백엔드 메뉴 Policies)은 2026-11-04에 생겼다.
1461
+ id: "rls-table-policy-list",
1462
+ domain: "rls",
1463
+ title: "\uD14C\uC774\uBE14\uBCC4 \uC815\uCC45 \uC870\uD68C",
1464
+ routes: ["GET projects/[projectId]/backend/policies"],
1465
+ ui: { kind: "built" },
1466
+ cli: { kind: "built", commands: ["naro policy rules"] },
1467
+ mcp: { kind: "built", tools: ["list_policy_rules"] }
1468
+ },
1469
+ {
1470
+ id: "rls-table-policy-set",
1471
+ domain: "rls",
1472
+ title: "\uD14C\uC774\uBE14\uC758 \uC561\uC158\uBCC4 \uADDC\uCE59 \uC124\uC815",
1473
+ routes: ["PUT projects/[projectId]/backend/policies/[table]"],
1474
+ ui: { kind: "built" },
1475
+ cli: { kind: "built", commands: ["naro policy set"] },
1476
+ // 규칙은 CREATE POLICY로, owner 규칙은 COMMENT ON POLICY p ON t IS 'naro:owner <column>'로 apply_migration에 쓴다.
1477
+ mcp: { kind: "built" }
1478
+ },
1479
+ {
1480
+ id: "rls-table-policy-remove",
1481
+ domain: "rls",
1482
+ title: "\uD14C\uC774\uBE14\uC758 \uC815\uCC45 \uC804\uBD80 \uC81C\uAC70",
1483
+ routes: ["DELETE projects/[projectId]/backend/policies/[table]"],
1484
+ ui: { kind: "built" },
1485
+ cli: { kind: "built", commands: ["naro policy remove"] },
1486
+ // apply_migration에 그 테이블의 DROP POLICY를 쓴다.
1487
+ mcp: { kind: "built" }
1488
+ },
1489
+ {
1490
+ id: "rls-policy-list",
1491
+ domain: "rls",
1492
+ title: "\uC815\uCC45 \uBAA9\uB85D(CREATE POLICY \uAF34)",
1493
+ routes: ["GET projects/[projectId]/backend/rls/policies"],
1494
+ ui: { kind: "built" },
1495
+ cli: { kind: "built", commands: ["naro policy list"] },
1496
+ mcp: { kind: "built", tools: ["list_policies"] }
1497
+ },
1498
+ {
1499
+ id: "rls-policy-create",
1500
+ domain: "rls",
1501
+ title: "\uC815\uCC45 \uB9CC\uB4E4\uAE30(CREATE POLICY)",
1502
+ routes: ["POST projects/[projectId]/backend/rls/policies"],
1503
+ ui: { kind: "built" },
1504
+ cli: { kind: "built", commands: ["naro policy create"] },
1505
+ // apply_migration·execute_sql이 CREATE POLICY를 받는다. 테이블과 같은 마이그레이션에 쓰라고 apply_migration이 안내한다.
1506
+ mcp: { kind: "built" }
1507
+ },
1508
+ {
1509
+ id: "rls-policy-replace",
1510
+ domain: "rls",
1511
+ title: "\uC815\uCC45 \uBC14\uAFB8\uAE30",
1512
+ routes: ["PUT projects/[projectId]/backend/rls/policies/[table]/[name]"],
1513
+ ui: { kind: "built" },
1514
+ // naro migration up·db push(client.db.applyMigration)나 naro db query
1515
+ // (client.db.query) 한 번에 DROP POLICY와 CREATE POLICY를 함께 쓴다.
1516
+ // MCP와 같은 경로다(PR #414 리뷰).
1517
+ cli: { kind: "built" },
1518
+ // apply_migration 한 번에 DROP POLICY와 CREATE POLICY를 함께 쓴다.
1519
+ mcp: { kind: "built" }
1520
+ },
1521
+ {
1522
+ id: "rls-policy-drop",
1523
+ domain: "rls",
1524
+ title: "\uC815\uCC45 \uC0AD\uC81C(DROP POLICY)",
1525
+ routes: ["DELETE projects/[projectId]/backend/rls/policies/[table]/[name]"],
1526
+ ui: { kind: "built" },
1527
+ cli: { kind: "built", commands: ["naro policy drop"] },
1528
+ // apply_migration·execute_sql이 DROP POLICY를 받는다.
1529
+ mcp: { kind: "built" }
1530
+ },
1531
+ {
1532
+ id: "storage-info",
1533
+ domain: "storage",
1534
+ title: "Storage \uC0C1\uD0DC",
1535
+ routes: ["GET projects/[projectId]/backend/storage"],
1536
+ ui: { kind: "built" },
1537
+ cli: { kind: "built", commands: ["naro storage"] },
1538
+ mcp: { kind: "built", tools: ["get_storage"] }
1539
+ },
1540
+ {
1541
+ id: "storage-enable",
1542
+ domain: "storage",
1543
+ title: "Storage \uCF1C\uAE30",
1544
+ routes: ["POST projects/[projectId]/backend/storage"],
1545
+ ui: { kind: "built" },
1546
+ cli: { kind: "built", commands: ["naro storage enable"] },
1547
+ mcp: { kind: "built", tools: ["enable_storage"] }
1548
+ },
1549
+ {
1550
+ id: "storage-disable",
1551
+ domain: "storage",
1552
+ title: "Storage \uB044\uAE30",
1553
+ routes: ["DELETE projects/[projectId]/backend/storage"],
1554
+ ui: { kind: "built" },
1555
+ cli: { kind: "built", commands: ["naro storage disable"] },
1556
+ mcp: { kind: "built", tools: ["disable_storage"] }
1557
+ },
1558
+ {
1559
+ id: "storage-bucket-list",
1560
+ domain: "storage",
1561
+ title: "\uBC84\uD0B7 \uBAA9\uB85D",
1562
+ routes: ["GET projects/[projectId]/backend/storage/buckets"],
1563
+ ui: { kind: "built" },
1564
+ cli: { kind: "built", commands: ["naro storage bucket list"] },
1565
+ mcp: { kind: "built", tools: ["list_storage_buckets"] }
1566
+ },
1567
+ {
1568
+ id: "storage-bucket-create",
1569
+ domain: "storage",
1570
+ title: "\uBC84\uD0B7 \uB9CC\uB4E4\uAE30",
1571
+ routes: ["POST projects/[projectId]/backend/storage/buckets"],
1572
+ ui: { kind: "built" },
1573
+ cli: { kind: "built", commands: ["naro storage bucket create"] },
1574
+ mcp: { kind: "built", tools: ["create_storage_bucket"] }
1575
+ },
1576
+ {
1577
+ id: "storage-bucket-get",
1578
+ domain: "storage",
1579
+ title: "\uBC84\uD0B7 \uC870\uD68C",
1580
+ routes: ["GET projects/[projectId]/backend/storage/buckets/[bucket]"],
1581
+ ui: { kind: "built" },
1582
+ // naro storage bucket update가 바꾸기 전에 읽는다(client.storage.getBucket).
1583
+ cli: { kind: "built" },
1584
+ mcp: { kind: "built", tools: ["get_storage_bucket"] }
1585
+ },
1586
+ {
1587
+ id: "storage-bucket-update",
1588
+ domain: "storage",
1589
+ title: "\uBC84\uD0B7 \uC124\uC815 \uBCC0\uACBD",
1590
+ routes: ["PATCH projects/[projectId]/backend/storage/buckets/[bucket]"],
1591
+ ui: { kind: "built" },
1592
+ cli: { kind: "built", commands: ["naro storage bucket update"] },
1593
+ mcp: { kind: "built", tools: ["update_storage_bucket"] }
1594
+ },
1595
+ {
1596
+ id: "storage-bucket-remove",
1597
+ domain: "storage",
1598
+ title: "\uBC84\uD0B7 \uC0AD\uC81C",
1599
+ routes: ["DELETE projects/[projectId]/backend/storage/buckets/[bucket]"],
1600
+ ui: { kind: "built" },
1601
+ cli: { kind: "built", commands: ["naro storage bucket remove"] },
1602
+ mcp: { kind: "built", tools: ["delete_storage_bucket"] }
1603
+ },
1604
+ {
1605
+ id: "storage-bucket-empty",
1606
+ domain: "storage",
1607
+ title: "\uBC84\uD0B7 \uBE44\uC6B0\uAE30",
1608
+ routes: [
1609
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/empty"
1610
+ ],
1611
+ ui: { kind: "built" },
1612
+ cli: { kind: "built", commands: ["naro storage bucket empty"] },
1613
+ mcp: { kind: "built", tools: ["empty_storage_bucket"] }
1614
+ },
1615
+ {
1616
+ id: "storage-object-list",
1617
+ domain: "storage",
1618
+ title: "\uAC1D\uCCB4 \uBAA9\uB85D",
1619
+ routes: [
1620
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/list"
1621
+ ],
1622
+ ui: { kind: "built" },
1623
+ cli: { kind: "built", commands: ["naro storage ls"] },
1624
+ mcp: { kind: "built", tools: ["list_storage_objects"] }
1625
+ },
1626
+ {
1627
+ id: "storage-object-remove",
1628
+ domain: "storage",
1629
+ title: "\uAC1D\uCCB4 \uC0AD\uC81C",
1630
+ routes: [
1631
+ "DELETE projects/[projectId]/backend/storage/buckets/[bucket]/objects"
1632
+ ],
1633
+ ui: { kind: "built" },
1634
+ cli: { kind: "built", commands: ["naro storage rm"] },
1635
+ mcp: { kind: "built", tools: ["delete_storage_objects"] }
1636
+ },
1637
+ {
1638
+ id: "storage-object-sign",
1639
+ domain: "storage",
1640
+ title: "\uB2E4\uC6B4\uB85C\uB4DC \uC11C\uBA85 URL",
1641
+ routes: [
1642
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/sign"
1643
+ ],
1644
+ ui: { kind: "built" },
1645
+ cli: { kind: "built", commands: ["naro storage sign"] },
1646
+ mcp: { kind: "built", tools: ["create_storage_signed_url"] }
1647
+ },
1648
+ {
1649
+ id: "storage-object-upload",
1650
+ domain: "storage",
1651
+ title: "\uC5C5\uB85C\uB4DC URL \uBC1C\uAE09",
1652
+ routes: [
1653
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/upload-url"
1654
+ ],
1655
+ ui: { kind: "built" },
1656
+ cli: { kind: "built", commands: ["naro storage cp"] },
1657
+ mcp: { kind: "built", tools: ["create_storage_upload_url"] }
1658
+ },
1659
+ {
1660
+ id: "storage-object-move",
1661
+ domain: "storage",
1662
+ title: "\uAC1D\uCCB4 \uC774\uB3D9",
1663
+ routes: [
1664
+ "POST projects/[projectId]/backend/storage/buckets/[bucket]/objects/move"
1665
+ ],
1666
+ ui: { kind: "built" },
1667
+ cli: { kind: "built", commands: ["naro storage mv"] },
1668
+ mcp: { kind: "built", tools: ["move_storage_object"] }
1669
+ },
1670
+ {
1671
+ id: "realtime-info",
1672
+ domain: "realtime",
1673
+ title: "Realtime \uC0C1\uD0DC\xB7\uC5F0\uACB0 \uC218",
1674
+ routes: ["GET projects/[projectId]/backend/realtime"],
1675
+ ui: { kind: "built" },
1676
+ cli: { kind: "built", commands: ["naro realtime"] },
1677
+ mcp: { kind: "built", tools: ["get_realtime"] }
1678
+ },
1679
+ {
1680
+ id: "realtime-enable",
1681
+ domain: "realtime",
1682
+ title: "Realtime \uCF1C\uAE30",
1683
+ routes: ["POST projects/[projectId]/backend/realtime"],
1684
+ ui: { kind: "built" },
1685
+ cli: { kind: "built", commands: ["naro realtime enable"] },
1686
+ mcp: { kind: "built", tools: ["enable_realtime"] }
1687
+ },
1688
+ {
1689
+ id: "realtime-table-list",
1690
+ domain: "realtime",
1691
+ title: "\uACF5\uAC1C \uD14C\uC774\uBE14 \uBAA9\uB85D",
1692
+ routes: ["GET projects/[projectId]/backend/realtime/tables"],
1693
+ ui: { kind: "built" },
1694
+ cli: { kind: "built", commands: ["naro realtime tables"] },
1695
+ // get_realtime이 공개 테이블을 상태와 함께 돌려준다.
1696
+ mcp: { kind: "built" }
1697
+ },
1698
+ {
1699
+ id: "realtime-table-add",
1700
+ domain: "realtime",
1701
+ title: "\uD14C\uC774\uBE14 \uBCC0\uACBD \uACF5\uAC1C",
1702
+ routes: ["PUT projects/[projectId]/backend/realtime/tables/[table]"],
1703
+ ui: { kind: "built" },
1704
+ cli: { kind: "built", commands: ["naro realtime tables add"] },
1705
+ mcp: { kind: "built", tools: ["publish_realtime_table"] }
1706
+ },
1707
+ {
1708
+ id: "realtime-table-remove",
1709
+ domain: "realtime",
1710
+ title: "\uD14C\uC774\uBE14 \uBCC0\uACBD \uACF5\uAC1C \uC911\uC9C0",
1711
+ routes: ["DELETE projects/[projectId]/backend/realtime/tables/[table]"],
1712
+ ui: { kind: "built" },
1713
+ cli: { kind: "built", commands: ["naro realtime tables remove"] },
1714
+ mcp: { kind: "built", tools: ["unpublish_realtime_table"] }
1715
+ },
1716
+ {
1717
+ // 대시보드 Functions 화면은 2026-11-04에 생겼다. 그 전의 built는 틀렸다.
1718
+ id: "function-list",
1719
+ domain: "functions",
1720
+ title: "\uD568\uC218 \uBAA9\uB85D",
1721
+ routes: ["GET projects/[projectId]/backend/functions"],
1722
+ ui: { kind: "built" },
1723
+ cli: { kind: "built", commands: ["naro functions"] },
1724
+ mcp: { kind: "built", tools: ["list_functions"] }
1725
+ },
1726
+ {
1727
+ id: "function-scaffold",
1728
+ domain: "functions",
1729
+ title: "\uD568\uC218 \uBF08\uB300 \uB9CC\uB4E4\uAE30",
1730
+ ui: {
1731
+ kind: "excluded",
1732
+ reason: "local-filesystem",
1733
+ note: "naro/functions/<name>/index.ts\uB97C \uC4F4\uB2E4. \uC11C\uBC84\uAC00 \uD560 \uC218 \uC5C6\uB2E4."
1734
+ },
1735
+ cli: { kind: "built", commands: ["naro functions new"] },
1736
+ mcp: {
1737
+ kind: "excluded",
1738
+ reason: "agent-native",
1739
+ note: "naro/functions/<name>/index.ts\uB97C \uC4F4\uB2E4. 2026-11-03 \uACB0\uC815."
1740
+ }
1741
+ },
1742
+ {
1743
+ id: "function-get",
1744
+ domain: "functions",
1745
+ title: "\uD568\uC218 \uC0C1\uC138",
1746
+ routes: ["GET projects/[projectId]/backend/functions/[name]"],
1747
+ ui: { kind: "built" },
1748
+ cli: { kind: "built", commands: ["naro functions versions"] },
1749
+ mcp: { kind: "built", tools: ["get_function"] }
1750
+ },
1751
+ {
1752
+ id: "function-update",
1753
+ domain: "functions",
1754
+ title: "\uD568\uC218 verify_jwt \uC124\uC815",
1755
+ routes: ["PATCH projects/[projectId]/backend/functions/[name]"],
1756
+ ui: { kind: "built" },
1757
+ cli: { kind: "built", commands: ["naro functions update"] },
1758
+ mcp: { kind: "built", tools: ["update_function"] }
1759
+ },
1760
+ {
1761
+ id: "function-delete",
1762
+ domain: "functions",
1763
+ title: "\uD568\uC218 \uC0AD\uC81C",
1764
+ routes: ["DELETE projects/[projectId]/backend/functions/[name]"],
1765
+ ui: { kind: "built" },
1766
+ cli: { kind: "built", commands: ["naro functions delete"] },
1767
+ mcp: { kind: "built", tools: ["delete_function"] }
1768
+ },
1769
+ {
1770
+ id: "function-version-upload",
1771
+ domain: "functions",
1772
+ title: "\uBC88\uB4E4\uD55C \uCF54\uB4DC\uB97C \uC0C8 \uBC84\uC804\uC73C\uB85C \uC62C\uB9AC\uAE30",
1773
+ routes: ["POST projects/[projectId]/backend/functions/[name]/versions"],
1774
+ ui: {
1775
+ kind: "excluded",
1776
+ reason: "local-filesystem",
1777
+ 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."
1778
+ },
1779
+ cli: { kind: "built", commands: ["naro functions deploy"] },
1780
+ mcp: { kind: "built", tools: ["deploy_function"] }
1781
+ },
1782
+ {
1783
+ id: "function-version-deploy",
1784
+ domain: "functions",
1785
+ title: "\uC800\uC7A5\uB41C \uBC84\uC804 \uBC30\uD3EC",
1786
+ routes: [
1787
+ "POST projects/[projectId]/backend/functions/[name]/versions/[versionId]/deploy"
1788
+ ],
1789
+ ui: { kind: "built" },
1790
+ // naro functions deploy가 올린 버전을 배포한다(client.functions.deploy).
1791
+ cli: { kind: "built" },
1792
+ // deploy_function이 naro-cli의 deployFunctions로 올린 버전을 배포한다.
1793
+ mcp: { kind: "built" }
1794
+ },
1795
+ {
1796
+ id: "function-rollback",
1797
+ domain: "functions",
1798
+ title: "\uC774\uC804 \uBC84\uC804\uC73C\uB85C \uB864\uBC31",
1799
+ routes: ["POST projects/[projectId]/backend/functions/[name]/rollback"],
1800
+ ui: { kind: "built" },
1801
+ cli: { kind: "built", commands: ["naro functions rollback"] },
1802
+ mcp: { kind: "built", tools: ["rollback_function"] }
1803
+ },
1804
+ {
1805
+ id: "function-logs",
1806
+ domain: "functions",
1807
+ title: "\uD568\uC218 \uB85C\uADF8",
1808
+ routes: ["GET projects/[projectId]/backend/functions/[name]/logs"],
1809
+ ui: { kind: "built" },
1810
+ cli: { kind: "built", commands: ["naro functions logs"] },
1811
+ mcp: { kind: "built", tools: ["get_function_logs"] }
1812
+ },
1813
+ {
1814
+ id: "function-invoke",
1815
+ domain: "functions",
1816
+ title: "\uD568\uC218 \uD638\uCD9C",
1817
+ routes: ["POST projects/[projectId]/backend/functions/[name]/invoke"],
1818
+ ui: { kind: "built" },
1819
+ cli: { kind: "built", commands: ["naro functions invoke"] },
1820
+ mcp: { kind: "built", tools: ["invoke_function"] }
1821
+ },
1822
+ {
1823
+ id: "function-secret-list",
1824
+ domain: "functions",
1825
+ title: "\uD568\uC218 \uC2DC\uD06C\uB9BF \uBAA9\uB85D",
1826
+ routes: ["GET projects/[projectId]/backend/functions-secrets"],
1827
+ ui: { kind: "built" },
1828
+ cli: { kind: "built", commands: ["naro functions secrets"] },
1829
+ mcp: { kind: "built", tools: ["list_function_secrets"] }
1830
+ },
1831
+ {
1832
+ id: "function-secret-set",
1833
+ domain: "functions",
1834
+ title: "\uD568\uC218 \uC2DC\uD06C\uB9BF \uC124\uC815",
1835
+ routes: ["PUT projects/[projectId]/backend/functions-secrets"],
1836
+ dashboard: "/[teamSlug]/projects/[projectSlug]/functions/secrets",
1837
+ ui: { kind: "built" },
1838
+ cli: {
1839
+ kind: "built",
1840
+ commands: ["naro functions secrets set", "naro functions secrets import"]
1841
+ },
1842
+ mcp: {
1843
+ kind: "excluded",
1844
+ reason: "secret-value",
1845
+ 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."
1846
+ }
1847
+ },
1848
+ // 라우트는 function-secret-set과 같은 PUT functions-secrets다. 한 라우트는
1849
+ // 한 기능에만 걸리므로 여기에는 적지 않는다.
1850
+ {
1851
+ id: "function-secret-unset",
1852
+ domain: "functions",
1853
+ title: "\uD568\uC218 \uC2DC\uD06C\uB9BF \uD574\uC81C",
1854
+ ui: { kind: "built" },
1855
+ cli: { kind: "built", commands: ["naro functions secrets unset"] },
1856
+ mcp: { kind: "built", tools: ["unset_function_secret"] }
1857
+ },
1858
+ {
1859
+ id: "function-sync",
1860
+ domain: "functions",
1861
+ title: "\uD568\uC218 \uC7AC\uB3D9\uAE30\uD654",
1862
+ routes: ["POST projects/[projectId]/backend/functions-sync"],
1863
+ ui: { kind: "built" },
1864
+ cli: { kind: "built", commands: ["naro functions secrets sync"] },
1865
+ mcp: { kind: "built", tools: ["sync_functions"] }
1866
+ },
1867
+ {
1868
+ id: "backend-info",
1869
+ domain: "backend",
1870
+ title: "Backend \uC0C1\uD0DC\xB7URL\xB7anon \uD0A4",
1871
+ routes: ["GET projects/[projectId]/backend"],
1872
+ ui: { kind: "built" },
1873
+ cli: { kind: "built", commands: ["naro backend"] },
1874
+ mcp: { kind: "built", tools: ["get_backend"] }
1875
+ },
1876
+ {
1877
+ id: "backend-enable",
1878
+ domain: "backend",
1879
+ title: "Backend \uCF1C\uAE30",
1880
+ routes: ["POST projects/[projectId]/backend/enable"],
1881
+ ui: { kind: "built" },
1882
+ cli: { kind: "built", commands: ["naro backend enable"] },
1883
+ mcp: { kind: "built", tools: ["enable_backend"] }
1884
+ },
1885
+ {
1886
+ id: "backend-provision",
1887
+ domain: "backend",
1888
+ title: "Backend \uD504\uB85C\uBE44\uC800\uB2DD \uC774\uC5B4\uAC00\uAE30",
1889
+ routes: ["POST projects/[projectId]/backend/provision"],
1890
+ ui: { kind: "built" },
1891
+ // naro backend enable이 멈춘 프로비저닝을 이어 간다.
1892
+ cli: { kind: "built" },
1893
+ // enable_backend가 같은 호출이다(client.backend.enable — 라우트 provision은 그 옛 이름).
1894
+ mcp: { kind: "built" }
1895
+ },
1896
+ {
1897
+ id: "backend-disable",
1898
+ domain: "backend",
1899
+ title: "Backend \uB044\uAE30",
1900
+ routes: ["POST projects/[projectId]/backend/disable"],
1901
+ ui: { kind: "built" },
1902
+ cli: { kind: "built", commands: ["naro backend disable"] },
1903
+ mcp: { kind: "built", tools: ["disable_backend"] }
1904
+ },
1905
+ {
1906
+ id: "backend-keys",
1907
+ domain: "backend",
1908
+ title: "API \uD0A4 \uC870\uD68C(service \uD0A4 \uD3EC\uD568)",
1909
+ routes: ["GET projects/[projectId]/backend/keys"],
1910
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1911
+ ui: { kind: "built" },
1912
+ cli: { kind: "built", commands: ["naro backend keys"] },
1913
+ mcp: {
1914
+ kind: "excluded",
1915
+ reason: "secret-value",
1916
+ note: "service \uD0A4\uAC00 \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1917
+ }
1918
+ },
1919
+ {
1920
+ id: "backend-keys-rotate",
1921
+ domain: "backend",
1922
+ title: "API \uD0A4 \uAD50\uCCB4",
1923
+ routes: ["POST projects/[projectId]/backend/keys/rotate"],
1924
+ // 2026-11-04 전에는 화면이 없었는데 built로 적혀 있었다.
1925
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1926
+ ui: { kind: "built" },
1927
+ cli: { kind: "built", commands: ["naro backend keys rotate"] },
1928
+ mcp: {
1929
+ kind: "excluded",
1930
+ reason: "irreversible",
1931
+ note: "\uC61B \uD0A4\uAC00 \uBC14\uB85C \uC8FD\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
1932
+ }
1933
+ },
1934
+ {
1935
+ id: "backend-signing-key-rotate",
1936
+ domain: "backend",
1937
+ title: "\uC11C\uBA85 \uD0A4 \uAD50\uCCB4",
1938
+ routes: ["POST projects/[projectId]/backend/signing-key/rotate"],
1939
+ dashboard: "/[teamSlug]/projects/[projectSlug]/settings",
1940
+ ui: { kind: "built" },
1941
+ cli: { kind: "built", commands: ["naro backend signing-key rotate"] },
1942
+ mcp: {
1943
+ kind: "excluded",
1944
+ reason: "irreversible",
1945
+ 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)."
1946
+ }
1947
+ },
1948
+ {
1949
+ id: "backend-auth-get",
1950
+ domain: "backend",
1951
+ title: "\uB85C\uADF8\uC778 \uC124\uC815 \uC870\uD68C",
1952
+ routes: ["GET projects/[projectId]/backend/auth"],
1953
+ ui: { kind: "built" },
1954
+ cli: {
1955
+ kind: "built",
1956
+ commands: ["naro backend auth", "naro backend auth redirects"]
1957
+ },
1958
+ mcp: { kind: "built", tools: ["get_backend_auth"] }
1959
+ },
1960
+ {
1961
+ id: "backend-auth-update",
1962
+ domain: "backend",
1963
+ title: "\uB85C\uADF8\uC778 \uC124\uC815 \uBCC0\uACBD",
1964
+ routes: ["PUT projects/[projectId]/backend/auth"],
1965
+ ui: { kind: "built" },
1966
+ cli: {
1967
+ kind: "built",
1968
+ commands: [
1969
+ "naro backend auth signups",
1970
+ "naro backend auth password",
1971
+ "naro backend auth email set",
1972
+ "naro backend auth oauth set",
1973
+ "naro backend auth redirects add",
1974
+ "naro backend auth redirects remove",
1975
+ "naro backend auth require-verification"
1976
+ ]
1977
+ },
1978
+ // 리다이렉트 제거·가입·확인 메일·비밀번호 길이만. 메일 API 키와
1979
+ // OAuth client secret은 대화에 남고(secret-value), 리다이렉트 추가는 재설정 토큰이
1980
+ // 그 origin으로 가므로(PR #434 리뷰) 대시보드와 CLI만 한다.
1981
+ mcp: { kind: "built", tools: ["update_backend_auth"] }
1982
+ },
1983
+ {
1984
+ id: "backend-auth-remove",
1985
+ domain: "backend",
1986
+ title: "\uB85C\uADF8\uC778 \uACF5\uAE09\uC790\xB7\uBA54\uC77C \uC124\uC815 \uC81C\uAC70",
1987
+ routes: ["DELETE projects/[projectId]/backend/auth"],
1988
+ ui: { kind: "built" },
1989
+ cli: {
1990
+ kind: "built",
1991
+ commands: [
1992
+ "naro backend auth email remove",
1993
+ "naro backend auth oauth remove"
1994
+ ]
1995
+ },
1996
+ mcp: { kind: "built", tools: ["remove_backend_auth"] }
1997
+ },
1998
+ {
1999
+ id: "backend-user-list",
2000
+ domain: "backend",
2001
+ title: "\uC720\uC800 \uBAA9\uB85D",
2002
+ routes: ["GET projects/[projectId]/backend/users"],
2003
+ ui: { kind: "built" },
2004
+ cli: { kind: "built", commands: ["naro backend users"] },
2005
+ mcp: { kind: "built", tools: ["list_backend_users"] }
2006
+ },
2007
+ {
2008
+ id: "backend-user-create",
2009
+ domain: "backend",
2010
+ title: "\uC720\uC800 \uB9CC\uB4E4\uAE30",
2011
+ routes: ["POST projects/[projectId]/backend/users"],
2012
+ dashboard: "/[teamSlug]/projects/[projectSlug]/auth",
2013
+ ui: { kind: "built" },
2014
+ cli: { kind: "built", commands: ["naro backend users create"] },
2015
+ mcp: {
2016
+ kind: "excluded",
2017
+ reason: "secret-value",
2018
+ note: "\uBE44\uBC00\uBC88\uD638\uAC00 \uB300\uD654\uC5D0 \uB0A8\uB294\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
2019
+ }
2020
+ },
2021
+ {
2022
+ id: "backend-user-delete",
2023
+ domain: "backend",
2024
+ title: "\uC720\uC800 \uC0AD\uC81C",
2025
+ routes: ["DELETE projects/[projectId]/backend/users/[userId]"],
2026
+ dashboard: "/[teamSlug]/projects/[projectSlug]/auth",
2027
+ ui: { kind: "built" },
2028
+ cli: { kind: "built", commands: ["naro backend users delete"] },
2029
+ mcp: {
2030
+ kind: "excluded",
2031
+ reason: "irreversible",
2032
+ note: "\uC720\uC800\uC640 \uC138\uC158\uC774 \uC0AC\uB77C\uC9C4\uB2E4. 2026-11-03 \uACB0\uC815(\uAD8C\uACE0\uB300\uB85C)."
2033
+ }
2034
+ },
2035
+ {
2036
+ id: "logs-runtime",
2037
+ domain: "logs",
2038
+ title: "\uB7F0\uD0C0\uC784 \uB85C\uADF8",
2039
+ routes: ["GET projects/[projectId]/logs"],
2040
+ ui: { kind: "built" },
2041
+ cli: { kind: "built", commands: ["naro logs"] },
2042
+ mcp: { kind: "built", tools: ["get_runtime_logs"] }
2043
+ },
2044
+ {
2045
+ id: "analytics",
2046
+ domain: "measurement",
2047
+ title: "Web Analytics",
2048
+ routes: ["GET projects/[projectId]/analytics"],
2049
+ ui: { kind: "built" },
2050
+ cli: { kind: "built", commands: ["naro analytics"] },
2051
+ mcp: { kind: "built", tools: ["get_analytics"] }
2052
+ },
2053
+ {
2054
+ id: "insights",
2055
+ domain: "measurement",
2056
+ title: "Speed Insights",
2057
+ routes: ["GET projects/[projectId]/speed-insights"],
2058
+ ui: { kind: "built" },
2059
+ cli: { kind: "built", commands: ["naro insights"] },
2060
+ mcp: { kind: "built", tools: ["get_speed_insights"] }
2061
+ },
2062
+ {
2063
+ id: "insights-toggle",
2064
+ domain: "measurement",
2065
+ title: "Speed Insights \uCE21\uC815 \uCF1C\uAE30\xB7\uB044\uAE30",
2066
+ routes: ["PUT projects/[projectId]/speed-insights"],
2067
+ ui: { kind: "built" },
2068
+ cli: {
2069
+ kind: "built",
2070
+ commands: ["naro insights enable", "naro insights disable"]
2071
+ },
2072
+ mcp: { kind: "built", tools: ["set_speed_insights"] }
2073
+ },
2074
+ {
2075
+ id: "insights-deployments",
2076
+ domain: "measurement",
2077
+ title: "Speed Insights \uBC30\uD3EC\uBCC4 \uBE44\uAD50",
2078
+ routes: ["GET projects/[projectId]/speed-insights/deployments"],
2079
+ ui: { kind: "built" },
2080
+ // naro insights가 배포별 표를 같이 읽는다(client.insights.overview).
2081
+ cli: { kind: "built" },
2082
+ // get_speed_insights가 route 없이 배포별 표를 같이 돌려준다(client.insights.overview).
2083
+ mcp: { kind: "built" }
2084
+ },
2085
+ {
2086
+ id: "insights-paths",
2087
+ domain: "measurement",
2088
+ title: "Speed Insights \uB77C\uC6B0\uD2B8\uBCC4 \uACBD\uB85C",
2089
+ routes: ["GET projects/[projectId]/speed-insights/paths"],
2090
+ ui: { kind: "built" },
2091
+ // naro insights --route가 읽는다(client.insights.paths).
2092
+ cli: { kind: "built" },
2093
+ // get_speed_insights에 route를 주면 경로를 돌려준다(client.insights.paths).
2094
+ mcp: { kind: "built" }
2095
+ },
2096
+ {
2097
+ id: "observability",
2098
+ domain: "measurement",
2099
+ title: "Observability",
2100
+ routes: ["GET projects/[projectId]/observability"],
2101
+ ui: { kind: "built" },
2102
+ cli: { kind: "built", commands: ["naro observability"] },
2103
+ mcp: { kind: "built", tools: ["get_observability"] }
2104
+ },
2105
+ {
2106
+ id: "firewall-list",
2107
+ domain: "firewall",
2108
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uBAA9\uB85D",
2109
+ routes: ["GET projects/[projectId]/firewall"],
2110
+ ui: { kind: "built" },
2111
+ cli: { kind: "built", commands: ["naro firewall"] },
2112
+ mcp: { kind: "built", tools: ["list_firewall_rules"] }
2113
+ },
2114
+ {
2115
+ id: "firewall-add",
2116
+ domain: "firewall",
2117
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uCD94\uAC00",
2118
+ routes: ["POST projects/[projectId]/firewall"],
2119
+ ui: { kind: "built" },
2120
+ cli: { kind: "built", commands: ["naro firewall add"] },
2121
+ mcp: { kind: "built", tools: ["add_firewall_rule"] }
2122
+ },
2123
+ {
2124
+ id: "firewall-update",
2125
+ domain: "firewall",
2126
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uC218\uC815",
2127
+ routes: ["PATCH projects/[projectId]/firewall/[ruleId]"],
2128
+ ui: { kind: "built" },
2129
+ cli: {
2130
+ kind: "built",
2131
+ commands: ["naro firewall enable", "naro firewall disable"]
2132
+ },
2133
+ mcp: { kind: "built", tools: ["set_firewall_rule_enabled"] }
2134
+ },
2135
+ {
2136
+ id: "firewall-remove",
2137
+ domain: "firewall",
2138
+ title: "\uBC29\uD654\uBCBD \uADDC\uCE59 \uC0AD\uC81C",
2139
+ routes: ["DELETE projects/[projectId]/firewall/[ruleId]"],
2140
+ ui: { kind: "built" },
2141
+ cli: { kind: "built", commands: ["naro firewall remove"] },
2142
+ mcp: { kind: "built", tools: ["remove_firewall_rule"] }
2143
+ },
2144
+ {
2145
+ id: "flag-list",
2146
+ domain: "flags",
2147
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uBAA9\uB85D",
2148
+ routes: ["GET projects/[projectId]/flags"],
2149
+ ui: { kind: "built" },
2150
+ cli: { kind: "built", commands: ["naro flags"] },
2151
+ mcp: { kind: "built", tools: ["list_flags"] }
2152
+ },
2153
+ {
2154
+ id: "flag-add",
2155
+ domain: "flags",
2156
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uCD94\uAC00",
2157
+ routes: ["POST projects/[projectId]/flags"],
2158
+ ui: { kind: "built" },
2159
+ cli: { kind: "built", commands: ["naro flags add"] },
2160
+ mcp: { kind: "built", tools: ["add_flag"] }
2161
+ },
2162
+ {
2163
+ id: "flag-update",
2164
+ domain: "flags",
2165
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uC218\uC815",
2166
+ routes: ["PATCH projects/[projectId]/flags/[flagId]"],
2167
+ ui: { kind: "built" },
2168
+ cli: { kind: "built", commands: ["naro flags on", "naro flags off"] },
2169
+ mcp: { kind: "built", tools: ["set_flag"] }
2170
+ },
2171
+ {
2172
+ id: "flag-remove",
2173
+ domain: "flags",
2174
+ title: "\uAE30\uB2A5 \uD50C\uB798\uADF8 \uC0AD\uC81C",
2175
+ routes: ["DELETE projects/[projectId]/flags/[flagId]"],
2176
+ ui: { kind: "built" },
2177
+ cli: { kind: "built", commands: ["naro flags remove"] },
2178
+ mcp: { kind: "built", tools: ["remove_flag"] }
2179
+ },
2180
+ {
2181
+ id: "dashboard-link",
2182
+ domain: "guidance",
2183
+ title: "CLI\xB7MCP\uB85C \uBABB \uD558\uB294 \uAE30\uB2A5\uC758 \uB300\uC2DC\uBCF4\uB4DC \uB9C1\uD06C",
2184
+ ui: {
2185
+ kind: "excluded",
2186
+ reason: "dashboard-itself",
2187
+ note: "\uC0AC\uC6A9\uC790\uAC00 \uC774\uBBF8 \uB300\uC2DC\uBCF4\uB4DC\uC5D0 \uC788\uB2E4."
2188
+ },
2189
+ cli: { kind: "built", commands: ["naro dashboard"] },
2190
+ mcp: { kind: "built", tools: ["dashboard_link"] }
2191
+ }
2192
+ ];
2193
+ function dashboardNeeds(path) {
2194
+ return {
2195
+ team: path.includes("[teamSlug]"),
2196
+ project: path.includes("[projectSlug]")
2197
+ };
2198
+ }
2199
+ function dashboardUrl(baseUrl, path, slugs) {
2200
+ const filled2 = path.replace("[teamSlug]", () => {
2201
+ if (!slugs.teamSlug) throw new Error(`${path} needs a team slug`);
2202
+ return encodeURIComponent(slugs.teamSlug);
2203
+ }).replace("[projectSlug]", () => {
2204
+ if (!slugs.projectSlug) throw new Error(`${path} needs a project slug`);
2205
+ return encodeURIComponent(slugs.projectSlug);
2206
+ });
2207
+ return `${baseUrl.replace(/\/+$/, "")}${filled2}`;
2208
+ }
2209
+
320
2210
  // ../../packages/core/src/deployment.ts
321
2211
  var PHASES = [
322
2212
  "QUEUED",
@@ -641,12 +2531,8 @@ var DEPLOYMENT_ERROR_CODES = [
641
2531
  "ENVIRONMENT_DELETED",
642
2532
  /** The edge refused the environment's protection change; the database change is rolled back. */
643
2533
  "ENVIRONMENT_PROTECTION_PUBLISH_FAILED",
644
- /** The variable came from the Supabase connection; only its card changes it (spec 2026-11-04 §6.3). */
645
- "ENV_MANAGED",
646
- /** The 10-minute Supabase sign-in cookie is gone or expired (spec 2026-11-04 §6.4). */
647
- "SUPABASE_SESSION_EXPIRED",
648
- /** The Supabase Management API refused or did not answer (spec 2026-11-04 §6.4). */
649
- "SUPABASE_API_FAILED"
2534
+ /** The variable is owned by the Resend connection; only it changes it (spec 2026-11-04 resend §6.4). */
2535
+ "ENV_MANAGED"
650
2536
  ];
651
2537
  var DeploymentError = class extends Error {
652
2538
  code;
@@ -16369,10 +18255,13 @@ export default {
16369
18255
  },
16370
18256
  };
16371
18257
  `;
16372
- var OUTPUT_DIR = "dist";
18258
+ var VINEXT_OUTPUT_DIR = "dist";
16373
18259
  var SERVER_DIR = "server";
16374
18260
  var CLIENT_DIR = "client";
16375
18261
  var WRANGLER_OUTPUT_NAME = "wrangler.json";
18262
+ function vinextWranglerConfigPath(appRoot) {
18263
+ return join6(appRoot, VINEXT_OUTPUT_DIR, SERVER_DIR, WRANGLER_OUTPUT_NAME);
18264
+ }
16376
18265
  async function exists3(path) {
16377
18266
  try {
16378
18267
  await access5(path);
@@ -16428,7 +18317,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
16428
18317
  compatibility_flags: ["nodejs_compat"],
16429
18318
  main: `./${GENERATED_WORKER_ENTRY}`,
16430
18319
  assets: {
16431
- directory: `${OUTPUT_DIR}/${CLIENT_DIR}`,
18320
+ directory: `${VINEXT_OUTPUT_DIR}/${CLIENT_DIR}`,
16432
18321
  binding: "ASSETS"
16433
18322
  }
16434
18323
  },
@@ -16527,14 +18416,14 @@ function moduleMatchers(rules) {
16527
18416
  }
16528
18417
  async function readVinextArtifact(appRoot, identity) {
16529
18418
  try {
16530
- const outputRoot = resolve2(appRoot, OUTPUT_DIR);
18419
+ const outputRoot = resolve2(appRoot, VINEXT_OUTPUT_DIR);
16531
18420
  const serverRoot = join6(outputRoot, SERVER_DIR);
16532
18421
  const clientRoot = join6(outputRoot, CLIENT_DIR);
16533
18422
  const configPath = join6(serverRoot, WRANGLER_OUTPUT_NAME);
16534
18423
  if (!await exists3(configPath)) {
16535
18424
  throw new DeploymentError(
16536
18425
  "ARTIFACT_INVALID",
16537
- `vinext build produced no ${OUTPUT_DIR}/${SERVER_DIR}/${WRANGLER_OUTPUT_NAME}.`
18426
+ `vinext build produced no ${VINEXT_OUTPUT_DIR}/${SERVER_DIR}/${WRANGLER_OUTPUT_NAME}.`
16538
18427
  );
16539
18428
  }
16540
18429
  const metadata = JSON.parse(
@@ -18606,13 +20495,8 @@ function createAwsMiddlewareCloudflareAppStrategy() {
18606
20495
  }
18607
20496
 
18608
20497
  // ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
18609
- import { join as join16 } from "path";
18610
20498
  var VINEXT_MIN_NEXT_MAJOR = 16;
18611
20499
  var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
18612
- var VINEXT_OUTPUT = {
18613
- outputRoot: "dist",
18614
- bundleDir: "dist/.naro-bundle"
18615
- };
18616
20500
  async function runCheck(context, observer) {
18617
20501
  if (!context.toolchain.vinext) return null;
18618
20502
  const held = [];
@@ -18762,13 +20646,8 @@ function createVinextCloudflareStrategy(options) {
18762
20646
  context,
18763
20647
  {
18764
20648
  appRoot: fork.appRoot,
18765
- wranglerConfigPath: join16(
18766
- fork.appRoot,
18767
- "dist",
18768
- "server",
18769
- "wrangler.json"
18770
- ),
18771
- ...VINEXT_OUTPUT
20649
+ wranglerConfigPath: vinextWranglerConfigPath(fork.appRoot),
20650
+ outputRoot: VINEXT_OUTPUT_DIR
18772
20651
  },
18773
20652
  observer
18774
20653
  );
@@ -18787,7 +20666,7 @@ function createVinextCloudflareStrategy(options) {
18787
20666
  // ../../packages/build/src/strategy/workspace.ts
18788
20667
  import { execFile } from "child_process";
18789
20668
  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";
20669
+ import { basename as basename2, dirname as dirname3, join as join16, relative as relative5, resolve as resolve6 } from "path";
18791
20670
  import { promisify } from "util";
18792
20671
  var execFileAsync = promisify(execFile);
18793
20672
  var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
@@ -18810,14 +20689,14 @@ async function copyInstalledPackages(source, target2) {
18810
20689
  const walk2 = async (dir) => {
18811
20690
  for (const entry of await readdir4(dir, { withFileTypes: true })) {
18812
20691
  if (!entry.isDirectory() || entry.name === ".git") continue;
18813
- const path = join17(dir, entry.name);
20692
+ const path = join16(dir, entry.name);
18814
20693
  if (entry.name === "node_modules") installs.push(path);
18815
20694
  else await walk2(path);
18816
20695
  }
18817
20696
  };
18818
20697
  await walk2(origin);
18819
20698
  for (const install of installs)
18820
- await copyTree(install, join17(target2, relative5(origin, install)));
20699
+ await copyTree(install, join16(target2, relative5(origin, install)));
18821
20700
  }
18822
20701
  async function copyTree(from, to) {
18823
20702
  await mkdir2(dirname3(to), { recursive: true });
@@ -18832,7 +20711,7 @@ async function copyTree(from, to) {
18832
20711
  await cp(from, to, { recursive: true, verbatimSymlinks: true });
18833
20712
  }
18834
20713
  function forkedAppRoot(checkout, appRoot, forkCheckout) {
18835
- return join17(forkCheckout, relative5(checkout, appRoot));
20714
+ return join16(forkCheckout, relative5(checkout, appRoot));
18836
20715
  }
18837
20716
 
18838
20717
  // ../../packages/build/src/local-build-executor.ts
@@ -18855,7 +20734,7 @@ async function withRouteList(artifact, list, appRoot, observer) {
18855
20734
  }
18856
20735
  }
18857
20736
  var defaultLocalBuildDependencies = {
18858
- createWorkspace: () => mkdtemp(join18(tmpdir(), "next-deploy-build-")),
20737
+ createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
18859
20738
  removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
18860
20739
  runCommand,
18861
20740
  detectPackageManager,
@@ -18890,7 +20769,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
18890
20769
  for (; ; ) {
18891
20770
  try {
18892
20771
  const source = await readFile13(
18893
- join18(dir, "node_modules", "next", "package.json"),
20772
+ join17(dir, "node_modules", "next", "package.json"),
18894
20773
  "utf8"
18895
20774
  );
18896
20775
  const version3 = JSON.parse(source).version;
@@ -19045,8 +20924,8 @@ var LocalBuildExecutor = class {
19045
20924
  }
19046
20925
  async build(input, observer, cache) {
19047
20926
  const workspace = await this.dependencies.createWorkspace();
19048
- const checkout = join18(workspace, "checkout");
19049
- const pmCacheDir = join18(workspace, ".pm-store");
20927
+ const checkout = join17(workspace, "checkout");
20928
+ const pmCacheDir = join17(workspace, ".pm-store");
19050
20929
  let cacheSeq = 0;
19051
20930
  const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
19052
20931
  const compressor = "zstd";
@@ -19055,7 +20934,7 @@ var LocalBuildExecutor = class {
19055
20934
  transport: cache,
19056
20935
  fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
19057
20936
  runCommand: this.dependencies.runCommand,
19058
- tmpFile: (suffix) => join18(workspace, `cache-${String(cacheSeq++)}${suffix}`),
20937
+ tmpFile: (suffix) => join17(workspace, `cache-${String(cacheSeq++)}${suffix}`),
19059
20938
  // One latch for the whole build so a disabled cache warns once.
19060
20939
  report: { unavailable: false }
19061
20940
  } : null;
@@ -19263,7 +21142,7 @@ var LocalBuildExecutor = class {
19263
21142
  }
19264
21143
  }
19265
21144
  await notifyStatus(observer, "BUILDING");
19266
- const buildCacheDir = resolved.preset.cachePath ? join18(appRoot, resolved.preset.cachePath) : null;
21145
+ const buildCacheDir = resolved.preset.cachePath ? join17(appRoot, resolved.preset.cachePath) : null;
19267
21146
  const buildCacheKey = sanitizeCacheKey(input.branch);
19268
21147
  if (cachePorts && buildCacheDir) {
19269
21148
  await tryRestore(
@@ -19287,13 +21166,13 @@ var LocalBuildExecutor = class {
19287
21166
  entries: fingerprintEntries,
19288
21167
  nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
19289
21168
  });
19290
- const pristine = join18(workspace, "pristine");
21169
+ const pristine = join17(workspace, "pristine");
19291
21170
  const fork = this.dependencies.forkWorkspace ?? defaultFork;
19292
21171
  await fork(checkout, pristine);
19293
21172
  let forks = 0;
19294
21173
  const resolverConcurrency = this.dependencies.resolverConcurrency ?? 1;
19295
21174
  const forkWorkspace = async (label2) => {
19296
- const target2 = join18(
21175
+ const target2 = join17(
19297
21176
  workspace,
19298
21177
  "forks",
19299
21178
  `${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
@@ -19589,12 +21468,12 @@ async function errorFromResponse(response, context) {
19589
21468
  // ../../packages/sdk/src/session.ts
19590
21469
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
19591
21470
  import { homedir } from "os";
19592
- import { join as join19 } from "path";
21471
+ import { join as join18 } from "path";
19593
21472
  function credentialsDir(home = homedir()) {
19594
- return join19(home, ".naro");
21473
+ return join18(home, ".naro");
19595
21474
  }
19596
21475
  function credentialsFile(home = homedir()) {
19597
- return join19(credentialsDir(home), "credentials.json");
21476
+ return join18(credentialsDir(home), "credentials.json");
19598
21477
  }
19599
21478
  function saveSession(session, home = homedir()) {
19600
21479
  mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
@@ -20198,44 +22077,6 @@ var FlagsApi = class {
20198
22077
  );
20199
22078
  }
20200
22079
  };
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
22080
 
20240
22081
  // ../../packages/sdk/src/ops.ts
20241
22082
  var BillingApi = class {
@@ -20269,32 +22110,6 @@ var ObservabilityApi = class {
20269
22110
  }
20270
22111
  };
20271
22112
 
20272
- // ../../packages/sdk/src/supabase-links.ts
20273
- var SupabaseApi = class {
20274
- constructor(client) {
20275
- this.client = client;
20276
- }
20277
- async links(projectId) {
20278
- const body = await this.client.request(
20279
- `/api/projects/${encodeURIComponent(projectId)}/supabase`,
20280
- { context: "Read Supabase links" }
20281
- );
20282
- return body.links;
20283
- }
20284
- /**
20285
- * Removes the link of one target (production, preview or an environment's
20286
- * slug), or every link the role may write when `target` is left out, with
20287
- * the env rows they hold. Answers how many went.
20288
- */
20289
- async disconnect(projectId, options = {}) {
20290
- const query = options.target === void 0 ? "" : `?target=${encodeURIComponent(options.target)}`;
20291
- return this.client.request(
20292
- `/api/projects/${encodeURIComponent(projectId)}/supabase${query}`,
20293
- { method: "DELETE", context: "Disconnect Supabase" }
20294
- );
20295
- }
20296
- };
20297
-
20298
22113
  // ../../packages/sdk/src/environments.ts
20299
22114
  function isReservedEnvTarget(value) {
20300
22115
  return RESERVED_ENVIRONMENT_SLUGS.includes(value);
@@ -20432,10 +22247,8 @@ var NaroClient = class _NaroClient {
20432
22247
  dns;
20433
22248
  firewall;
20434
22249
  flags;
20435
- bindings;
20436
22250
  billing;
20437
22251
  observability;
20438
- supabase;
20439
22252
  logs;
20440
22253
  db;
20441
22254
  backend;
@@ -20462,10 +22275,8 @@ var NaroClient = class _NaroClient {
20462
22275
  this.dns = new DnsApi(this);
20463
22276
  this.firewall = new FirewallApi(this);
20464
22277
  this.flags = new FlagsApi(this);
20465
- this.bindings = new BindingsApi(this);
20466
22278
  this.billing = new BillingApi(this);
20467
22279
  this.observability = new ObservabilityApi(this);
20468
- this.supabase = new SupabaseApi(this);
20469
22280
  this.logs = new LogsApi(this);
20470
22281
  this.db = new DbApi(this);
20471
22282
  this.backend = new BackendApi(this);
@@ -20987,9 +22798,10 @@ var EnvApi = class {
20987
22798
  /**
20988
22799
  * The names the env route refuses as a key — `409 VALIDATION_FAILED`
20989
22800
  * `<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
22801
+ * them: `ASSETS` (the static files) and, on a project using the managed
22802
+ * database (`database_provider` is `d1`), `DB`. The bindings a user
22803
+ * declared (R2·KV·D1) are gone since 2026-11-04, and so is the route that
22804
+ * listed them. For a caller that sends
20993
22805
  * many entries in several requests (`naro env import`) and has to refuse
20994
22806
  * before the first one is saved. A control plane whose project row has no
20995
22807
  * `database_provider` reads as having no managed database, so `DB` is only
@@ -20997,20 +22809,8 @@ var EnvApi = class {
20997
22809
  */
20998
22810
  async takenBindingNames(projectId) {
20999
22811
  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
- ]);
22812
+ const project2 = await this.client.request(base2, { context: "Read project" });
22813
+ const names = /* @__PURE__ */ new Set([STATIC_ASSETS_BINDING]);
21014
22814
  if (project2.project.database_provider === "d1")
21015
22815
  names.add(MANAGED_DATABASE_BINDING);
21016
22816
  return [...names];
@@ -22686,6 +24486,54 @@ var FunctionsApi = class {
22686
24486
  }
22687
24487
  };
22688
24488
 
24489
+ // ../../packages/sdk/src/dashboard.ts
24490
+ function dashboardCapability(id) {
24491
+ const found = CAPABILITIES.find((capability) => capability.id === id);
24492
+ if (!found || typeof found.dashboard !== "string")
24493
+ throw new NaroError(
24494
+ "VALIDATION_FAILED",
24495
+ `No dashboard page for "${id}". Run without a capability to list the ones that have one.`
24496
+ );
24497
+ return found;
24498
+ }
24499
+ async function dashboardLink(client, capability, target2, options) {
24500
+ const needs = dashboardNeeds(capability.dashboard);
24501
+ let teamSlug = target2.teamSlug;
24502
+ let projectSlug;
24503
+ if (needs.project) {
24504
+ if (!target2.projectId)
24505
+ throw new NaroError(
24506
+ "PROJECT_NOT_LINKED",
24507
+ "This page belongs to a project: pass its id, or link the directory first."
24508
+ );
24509
+ const [project2, teams] = await Promise.all([
24510
+ client.projects.get(target2.projectId),
24511
+ client.teams.list()
24512
+ ]);
24513
+ const team = teams.find((candidate) => candidate.id === project2.teamId);
24514
+ if (!team)
24515
+ throw new NaroError(
24516
+ "NOT_FOUND",
24517
+ `The team of project ${project2.slug} is not one of yours.`
24518
+ );
24519
+ teamSlug = team.slug;
24520
+ projectSlug = project2.slug;
24521
+ } else if (needs.team && !teamSlug) {
24522
+ throw new NaroError("TEAM_REQUIRED", "This page belongs to a team.");
24523
+ }
24524
+ const support = capability[options.client];
24525
+ return {
24526
+ capability: capability.id,
24527
+ title: capability.title,
24528
+ url: dashboardUrl(options.baseUrl, capability.dashboard, {
24529
+ teamSlug,
24530
+ projectSlug
24531
+ }),
24532
+ reason: support.kind === "excluded" ? { code: support.reason, why: REASON_CODES[support.reason].why } : null,
24533
+ cli: capability.cli.kind === "built" ? capability.cli.commands ?? [] : []
24534
+ };
24535
+ }
24536
+
22689
24537
  // ../../packages/sdk/src/database.ts
22690
24538
  var BOOKMARK_RE = /^[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{8}-[0-9a-f]{32}$/;
22691
24539
  var BOOKMARK_FORMAT_MESSAGE = "bookmark must look like 00000001-00000008-000050ed-272153b8520cfb93b49834d93a1a1ab3 (four hyphen-separated hex groups, 59 characters).";
@@ -22736,14 +24584,14 @@ function summarizeStatement(statement2) {
22736
24584
 
22737
24585
  // ../../packages/sdk/src/dotenv.ts
22738
24586
  import { existsSync, readFileSync as readFileSync2 } from "fs";
22739
- import { join as join20 } from "path";
24587
+ import { join as join19 } from "path";
22740
24588
  var QUOTES2 = /* @__PURE__ */ new Set(["'", '"', "`"]);
22741
24589
  function parseDotenv(text3) {
22742
24590
  return parseDotenvDetailed(text3).vars;
22743
24591
  }
22744
24592
  function readProjectEnv(dir) {
22745
24593
  for (const name of [".env.local", ".env.production", ".env"]) {
22746
- const path = join20(dir, name);
24594
+ const path = join19(dir, name);
22747
24595
  if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
22748
24596
  }
22749
24597
  return {};
@@ -22797,7 +24645,7 @@ function serializeDotenv(vars) {
22797
24645
 
22798
24646
  // ../../packages/sdk/src/env-scan.ts
22799
24647
  import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
22800
- import { join as join21 } from "path";
24648
+ import { join as join20 } from "path";
22801
24649
  var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
22802
24650
  ".ts",
22803
24651
  ".tsx",
@@ -22852,7 +24700,7 @@ function collect(dir, out) {
22852
24700
  for (const entry of entries) {
22853
24701
  if (out.length >= MAX_FILES) return;
22854
24702
  if (entry.isSymbolicLink()) continue;
22855
- const path = join21(dir, entry.name);
24703
+ const path = join20(dir, entry.name);
22856
24704
  if (entry.isDirectory()) {
22857
24705
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
22858
24706
  continue;
@@ -22884,7 +24732,7 @@ function scanEnvReferences(dir) {
22884
24732
  }
22885
24733
  }
22886
24734
  for (const name of EXAMPLE_FILES) {
22887
- const path = join21(dir, name);
24735
+ const path = join20(dir, name);
22888
24736
  if (!existsSync2(path)) continue;
22889
24737
  let parsed;
22890
24738
  try {
@@ -22902,11 +24750,11 @@ function scanEnvReferences(dir) {
22902
24750
 
22903
24751
  // ../../packages/sdk/src/framework.ts
22904
24752
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
22905
- import { join as join22 } from "path";
24753
+ import { join as join21 } from "path";
22906
24754
  function readPackageJson2(dir) {
22907
24755
  try {
22908
24756
  return JSON.parse(
22909
- readFileSync4(join22(dir, "package.json"), "utf8")
24757
+ readFileSync4(join21(dir, "package.json"), "utf8")
22910
24758
  );
22911
24759
  } catch {
22912
24760
  return null;
@@ -22917,7 +24765,7 @@ function hasDependency(pkg, name) {
22917
24765
  }
22918
24766
  function hasConfig(dir, base2, evidence) {
22919
24767
  for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
22920
- if (existsSync3(join22(dir, `${base2}.${ext}`))) {
24768
+ if (existsSync3(join21(dir, `${base2}.${ext}`))) {
22921
24769
  evidence.push(`${base2}.${ext}`);
22922
24770
  return true;
22923
24771
  }
@@ -23025,8 +24873,8 @@ function detectFramework2(dir) {
23025
24873
  if (!byDependency && !byConfig) continue;
23026
24874
  if (byDependency) evidence.push(`package.json: ${detector.dependency}`);
23027
24875
  if (detector.framework === "nextjs") {
23028
- if (existsSync3(join22(dir, "app"))) evidence.push("app/");
23029
- if (existsSync3(join22(dir, "pages"))) evidence.push("pages/");
24876
+ if (existsSync3(join21(dir, "app"))) evidence.push("app/");
24877
+ if (existsSync3(join21(dir, "pages"))) evidence.push("pages/");
23030
24878
  }
23031
24879
  return {
23032
24880
  framework: detector.framework,
@@ -23037,7 +24885,7 @@ function detectFramework2(dir) {
23037
24885
  deployable: true
23038
24886
  };
23039
24887
  }
23040
- if (!pkg && existsSync3(join22(dir, "index.html"))) {
24888
+ if (!pkg && existsSync3(join21(dir, "index.html"))) {
23041
24889
  evidence.push("index.html (no package.json)");
23042
24890
  return {
23043
24891
  framework: "static",
@@ -31847,12 +33695,12 @@ import {
31847
33695
  rmSync as rmSync2,
31848
33696
  writeFileSync as writeFileSync2
31849
33697
  } from "fs";
31850
- import { join as join23 } from "path";
33698
+ import { join as join22 } from "path";
31851
33699
  function linkDir(dir) {
31852
- return join23(dir, ".naro");
33700
+ return join22(dir, ".naro");
31853
33701
  }
31854
33702
  function linkFile(dir) {
31855
- return join23(linkDir(dir), "project.json");
33703
+ return join22(linkDir(dir), "project.json");
31856
33704
  }
31857
33705
  function readProjectLink(dir) {
31858
33706
  try {
@@ -31874,7 +33722,7 @@ function writeProjectLink(dir, link) {
31874
33722
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
31875
33723
  `);
31876
33724
  writeFileSync2(
31877
- join23(linkDir(dir), "README.txt"),
33725
+ join22(linkDir(dir), "README.txt"),
31878
33726
  "This directory links the project to Naro and should not be committed.\n"
31879
33727
  );
31880
33728
  }
@@ -31884,9 +33732,9 @@ function removeProjectLink(dir) {
31884
33732
  return true;
31885
33733
  }
31886
33734
  function ensureGitignoreHasNaro(dir) {
31887
- const gitignorePath = join23(dir, ".gitignore");
33735
+ const gitignorePath = join22(dir, ".gitignore");
31888
33736
  const hasGitignore = existsSync4(gitignorePath);
31889
- if (!hasGitignore && !existsSync4(join23(dir, ".git"))) return false;
33737
+ if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
31890
33738
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
31891
33739
  const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
31892
33740
  if (covered) return false;
@@ -32671,7 +34519,7 @@ function usageStatus(report, currentMonth, requests) {
32671
34519
 
32672
34520
  // src/env-consent.ts
32673
34521
  import { existsSync as existsSync6 } from "fs";
32674
- import { join as join24 } from "path";
34522
+ import { join as join23 } from "path";
32675
34523
  var DOTENV_FILES = [".env.local", ".env.production", ".env"];
32676
34524
  var PUBLIC_PREFIX = "NEXT_PUBLIC_";
32677
34525
  function maskSecretValue(value) {
@@ -32683,7 +34531,7 @@ function maskEnvValue(key, value) {
32683
34531
  return maskSecretValue(value);
32684
34532
  }
32685
34533
  function findDotenvFile(dir) {
32686
- for (const name of DOTENV_FILES) if (existsSync6(join24(dir, name))) return name;
34534
+ for (const name of DOTENV_FILES) if (existsSync6(join23(dir, name))) return name;
32687
34535
  return null;
32688
34536
  }
32689
34537
  function envAddGuidance(keys, environment) {
@@ -32947,13 +34795,13 @@ async function uploadArtifact(artifact, opts) {
32947
34795
 
32948
34796
  // src/version.ts
32949
34797
  import { readFileSync as readFileSync6 } from "fs";
32950
- import { dirname as dirname5, join as join25 } from "path";
34798
+ import { dirname as dirname5, join as join24 } from "path";
32951
34799
  import { fileURLToPath } from "url";
32952
34800
  function version2() {
32953
34801
  try {
32954
34802
  const pkg = JSON.parse(
32955
34803
  readFileSync6(
32956
- join25(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"),
34804
+ join24(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"),
32957
34805
  "utf8"
32958
34806
  )
32959
34807
  );
@@ -32982,7 +34830,7 @@ import {
32982
34830
  basename as basename3,
32983
34831
  dirname as dirname6,
32984
34832
  isAbsolute as isAbsolute4,
32985
- join as join26,
34833
+ join as join25,
32986
34834
  relative as relative6,
32987
34835
  resolve as resolve10,
32988
34836
  sep as sep5
@@ -33109,12 +34957,12 @@ async function copyWorkingTree(source, checkout) {
33109
34957
  keptLinks,
33110
34958
  async ({ path, text: text3 }) => {
33111
34959
  try {
33112
- return inside(await realpath3(join26(checkout, path)), root);
34960
+ return inside(await realpath3(join25(checkout, path)), root);
33113
34961
  } catch (error51) {
33114
34962
  return error51.code === "ENOENT" && climbsOnlyFirst(text3);
33115
34963
  }
33116
34964
  },
33117
- ({ path }) => unlink(join26(checkout, path))
34965
+ ({ path }) => unlink(join25(checkout, path))
33118
34966
  );
33119
34967
  linksElsewhere.push(...strays.map(({ path }) => path));
33120
34968
  const committed = await committedEnvFiles(sourceDir);
@@ -33128,7 +34976,7 @@ async function copyWorkingTree(source, checkout) {
33128
34976
  outsideProject.push(path);
33129
34977
  continue;
33130
34978
  }
33131
- const target2 = join26(checkout, path);
34979
+ const target2 = join25(checkout, path);
33132
34980
  if (!inside(await realAncestor(dirname6(target2)), root)) {
33133
34981
  outsideProject.push(path);
33134
34982
  continue;
@@ -33153,16 +35001,16 @@ async function copyWorkingTree(source, checkout) {
33153
35001
  continue;
33154
35002
  }
33155
35003
  await writeFile7(target2, content, { flag: "wx" });
33156
- const local = await readLocal(join26(sourceDir, path));
35004
+ const local = await readLocal(join25(sourceDir, path));
33157
35005
  if (!local || !sameText(local, content)) committedVersion.push(path);
33158
35006
  }
33159
35007
  const envStrays = await keptOnly(
33160
35008
  envLinks,
33161
35009
  async (path) => {
33162
- const real = await realpath3(join26(checkout, path)).catch(() => null);
35010
+ const real = await realpath3(join25(checkout, path)).catch(() => null);
33163
35011
  return real !== null && inside(real, root);
33164
35012
  },
33165
- (path) => unlink(join26(checkout, path))
35013
+ (path) => unlink(join25(checkout, path))
33166
35014
  );
33167
35015
  outsideProject.push(...envStrays);
33168
35016
  const inCommit = new Set(
@@ -33215,7 +35063,7 @@ function forEnvironment(text3, environment) {
33215
35063
 
33216
35064
  // src/commands/migration.ts
33217
35065
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
33218
- import { join as join27, relative as relative7 } from "path";
35066
+ import { join as join26, relative as relative7 } from "path";
33219
35067
 
33220
35068
  // src/prompt.ts
33221
35069
  import { createInterface as createInterface2 } from "readline";
@@ -33389,7 +35237,7 @@ function warningLines(message) {
33389
35237
  }
33390
35238
  function policyProblems(dir, names) {
33391
35239
  return names.flatMap((name) => {
33392
- const split2 = splitStatements(readFileSync7(join27(dir, name), "utf8"));
35240
+ const split2 = splitStatements(readFileSync7(join26(dir, name), "utf8"));
33393
35241
  if (!split2.ok) return [];
33394
35242
  const planned = planPolicyMigration(
33395
35243
  split2.statements.map((statement2) => statement2.text)
@@ -33420,7 +35268,7 @@ async function runMigrationNew(context, deps = processStdin) {
33420
35268
  const body = fromStdin ? piped.endsWith("\n") ? piped : `${piped}
33421
35269
  ` : `-- Migration: ${fileName}
33422
35270
  `;
33423
- const path = join27(dir, fileName);
35271
+ const path = join26(dir, fileName);
33424
35272
  writeFileSync3(path, body, { flag: "wx" });
33425
35273
  const relPath = relativeToCwd(context, path);
33426
35274
  context.output.result({ file: fileName, path: relPath, fromStdin }, [
@@ -33449,7 +35297,7 @@ async function applyMigrationFiles(context, client, projectId, dir, names) {
33449
35297
  const applied = [];
33450
35298
  const warnings = [];
33451
35299
  for (const name of names) {
33452
- const sql = readFileSync7(join27(dir, name), "utf8");
35300
+ const sql = readFileSync7(join26(dir, name), "utf8");
33453
35301
  let answer;
33454
35302
  try {
33455
35303
  answer = await client.db.applyMigration(projectId, { name, sql });
@@ -33661,7 +35509,7 @@ var BUNDLE_MESSAGES = {
33661
35509
 
33662
35510
  // ../../packages/functions-bundler/src/discover.ts
33663
35511
  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";
35512
+ import { join as join27, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
33665
35513
 
33666
35514
  // ../../packages/functions-bundler/src/limits.ts
33667
35515
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
@@ -33685,7 +35533,7 @@ function isEntrypoint(value) {
33685
35533
  function functionsFolder(options) {
33686
35534
  const root = resolve11(options.cwd, options.root ?? ".");
33687
35535
  const explicit = options.dir !== void 0;
33688
- const dir = explicit ? resolve11(options.cwd, options.dir) : join28(root, "naro", "functions");
35536
+ const dir = explicit ? resolve11(options.cwd, options.dir) : join27(root, "naro", "functions");
33689
35537
  const label2 = relative8(options.cwd, dir).split(sep6).join("/") || ".";
33690
35538
  return { dir, label: label2, root, explicit };
33691
35539
  }
@@ -33694,7 +35542,7 @@ function listFunctionNames(folder) {
33694
35542
  try {
33695
35543
  entries = readdirSync3(folder.dir, { withFileTypes: true });
33696
35544
  } catch {
33697
- if (!folder.explicit && existsSync7(join28(folder.root, "supabase", "functions")))
35545
+ if (!folder.explicit && existsSync7(join27(folder.root, "supabase", "functions")))
33698
35546
  throw new FunctionBundleError(
33699
35547
  "VALIDATION_FAILED",
33700
35548
  BUNDLE_MESSAGES.supabaseFound
@@ -33715,7 +35563,7 @@ function listFunctionNames(folder) {
33715
35563
  return names;
33716
35564
  }
33717
35565
  function readFunctionJson(dir, name) {
33718
- const file2 = join28(dir, "function.json");
35566
+ const file2 = join27(dir, "function.json");
33719
35567
  if (!existsSync7(file2)) return { verifyJwt: void 0, entrypoint: void 0 };
33720
35568
  let parsed;
33721
35569
  try {
@@ -33759,7 +35607,7 @@ function readFunctionSource(folder, name) {
33759
35607
  "VALIDATION_FAILED",
33760
35608
  BUNDLE_MESSAGES.badName(name)
33761
35609
  );
33762
- const dir = join28(folder.dir, name);
35610
+ const dir = join27(folder.dir, name);
33763
35611
  if (!statSync2(dir, { throwIfNoEntry: false })?.isDirectory())
33764
35612
  throw new FunctionBundleError(
33765
35613
  "VALIDATION_FAILED",
@@ -33768,14 +35616,14 @@ function readFunctionSource(folder, name) {
33768
35616
  const config2 = readFunctionJson(dir, name);
33769
35617
  let entrypoint;
33770
35618
  if (config2.entrypoint !== void 0) {
33771
- if (!existsSync7(join28(dir, ...config2.entrypoint.split("/"))))
35619
+ if (!existsSync7(join27(dir, ...config2.entrypoint.split("/"))))
33772
35620
  throw new FunctionBundleError(
33773
35621
  "FUNCTION_BUNDLE_FAILED",
33774
35622
  BUNDLE_MESSAGES.entryMissing(name, config2.entrypoint)
33775
35623
  );
33776
35624
  entrypoint = config2.entrypoint;
33777
35625
  } else {
33778
- const found = ENTRY_FILES.find((file2) => existsSync7(join28(dir, file2)));
35626
+ const found = ENTRY_FILES.find((file2) => existsSync7(join27(dir, file2)));
33779
35627
  if (found === void 0)
33780
35628
  throw new FunctionBundleError(
33781
35629
  "FUNCTION_BUNDLE_FAILED",
@@ -33786,14 +35634,14 @@ function readFunctionSource(folder, name) {
33786
35634
  return {
33787
35635
  name,
33788
35636
  dir,
33789
- entry: join28(dir, ...entrypoint.split("/")),
35637
+ entry: join27(dir, ...entrypoint.split("/")),
33790
35638
  entrypoint,
33791
35639
  verifyJwt: config2.verifyJwt
33792
35640
  };
33793
35641
  }
33794
35642
  function configTomlWarning(folder) {
33795
- if (!folder.dir.endsWith(join28("supabase", "functions"))) return null;
33796
- const file2 = join28(folder.dir, "..", "config.toml");
35643
+ if (!folder.dir.endsWith(join27("supabase", "functions"))) return null;
35644
+ const file2 = join27(folder.dir, "..", "config.toml");
33797
35645
  if (!existsSync7(file2)) return null;
33798
35646
  return /^\s*verify_jwt\s*=/m.test(readFileSync8(file2, "utf8")) ? BUNDLE_MESSAGES.supabaseVerifyJwt(folder.label) : null;
33799
35647
  }
@@ -33999,11 +35847,11 @@ ${injects.map(
33999
35847
 
34000
35848
  // ../../packages/functions-bundler/src/deno-imports.ts
34001
35849
  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";
35850
+ import { dirname as dirname9, isAbsolute as isAbsolute5, join as join29, relative as relative9, sep as sep8 } from "path";
34003
35851
 
34004
35852
  // ../../packages/functions-bundler/src/import-map.ts
34005
35853
  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";
35854
+ import { basename as basename4, dirname as dirname8, join as join28, resolve as resolve12, sep as sep7 } from "path";
34007
35855
  import { fileURLToPath as fileURLToPath3 } from "url";
34008
35856
 
34009
35857
  // ../../packages/functions-bundler/src/printable.ts
@@ -34075,7 +35923,7 @@ function readImportMapFile(file2, label2) {
34075
35923
  }
34076
35924
  function readImportMap(functionDir, fallback, labelOf) {
34077
35925
  for (const name of FILES) {
34078
- const file2 = join29(functionDir, name);
35926
+ const file2 = join28(functionDir, name);
34079
35927
  if (!existsSync8(file2)) continue;
34080
35928
  const map2 = load(file2, labelOf(file2));
34081
35929
  if (map2 !== null) return map2;
@@ -34218,7 +36066,7 @@ function packageDir(from, projectRoot) {
34218
36066
  let current = from;
34219
36067
  while (isInside(current, projectRoot)) {
34220
36068
  const inNodeModules = fromRoot(projectRoot, current).split("/").includes("node_modules");
34221
- if (!inNodeModules && existsSync9(join30(current, "package.json")))
36069
+ if (!inNodeModules && existsSync9(join29(current, "package.json")))
34222
36070
  return fromRoot(projectRoot, current) || ".";
34223
36071
  const up = dirname9(current);
34224
36072
  if (up === current) break;
@@ -34230,7 +36078,7 @@ function isInstalled(from, pkg) {
34230
36078
  if (pkg === "") return false;
34231
36079
  let current = from;
34232
36080
  for (; ; ) {
34233
- if (existsSync9(join30(current, "node_modules", pkg))) return true;
36081
+ if (existsSync9(join29(current, "node_modules", pkg))) return true;
34234
36082
  const up = dirname9(current);
34235
36083
  if (up === current) return false;
34236
36084
  current = up;
@@ -34240,7 +36088,7 @@ var NODE_MODULES = /[\\/]node_modules[\\/]/;
34240
36088
  function installedVersion(resolved, pkg) {
34241
36089
  let current = dirname9(resolved);
34242
36090
  for (; ; ) {
34243
- const file2 = join30(current, "package.json");
36091
+ const file2 = join29(current, "package.json");
34244
36092
  if (existsSync9(file2)) {
34245
36093
  try {
34246
36094
  const manifest2 = JSON.parse(readFileSync10(file2, "utf8"));
@@ -34424,7 +36272,7 @@ function denoImportsPlugin(options) {
34424
36272
 
34425
36273
  // ../../packages/functions-bundler/src/bundle.ts
34426
36274
  import { createHash as createHash4 } from "crypto";
34427
- import { join as join31, relative as relative10, sep as sep9 } from "path";
36275
+ import { join as join30, relative as relative10, sep as sep9 } from "path";
34428
36276
  import { gzipSync } from "zlib";
34429
36277
 
34430
36278
  // ../../packages/functions-runtime/dist/index.js
@@ -34500,7 +36348,7 @@ async function bundleFunction(source, folder, options = {}) {
34500
36348
  result = await build({
34501
36349
  absWorkingDir: folder.root,
34502
36350
  entryPoints: [source.entry],
34503
- outfile: join31(source.dir, NARO_USER_MODULE),
36351
+ outfile: join30(source.dir, NARO_USER_MODULE),
34504
36352
  bundle: true,
34505
36353
  write: false,
34506
36354
  format: "esm",
@@ -35070,7 +36918,7 @@ function slugify2(value) {
35070
36918
  async function projectName(sourceDir) {
35071
36919
  try {
35072
36920
  const pkg = JSON.parse(
35073
- await readFile15(join32(sourceDir, "package.json"), "utf8")
36921
+ await readFile15(join31(sourceDir, "package.json"), "utf8")
35074
36922
  );
35075
36923
  if (pkg.name) return pkg.name;
35076
36924
  } catch {
@@ -35080,7 +36928,7 @@ async function projectName(sourceDir) {
35080
36928
  function resolveBin(name) {
35081
36929
  let dir = dirname10(fileURLToPath4(import.meta.url));
35082
36930
  for (; ; ) {
35083
- const candidate = join32(dir, "node_modules", ".bin", name);
36931
+ const candidate = join31(dir, "node_modules", ".bin", name);
35084
36932
  if (existsSync10(candidate)) return candidate;
35085
36933
  const parent = dirname10(dir);
35086
36934
  if (parent === dir) return name;
@@ -35147,7 +36995,7 @@ function workingTreeNotices(copy) {
35147
36995
  }
35148
36996
  function localDependencies(sourceDir, say = () => void 0) {
35149
36997
  return {
35150
- createWorkspace: () => mkdtemp2(join32(tmpdir2(), "naro-build-")),
36998
+ createWorkspace: () => mkdtemp2(join31(tmpdir2(), "naro-build-")),
35151
36999
  removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
35152
37000
  async runCommand(spec, observer) {
35153
37001
  if (spec.command === "git" && spec.args[0] === "clone") {
@@ -35402,6 +37250,8 @@ function deploymentUrl(detail, ctx, production) {
35402
37250
  }
35403
37251
 
35404
37252
  export {
37253
+ CAPABILITIES,
37254
+ dashboardNeeds,
35405
37255
  parseDotenvDetailed,
35406
37256
  FUNCTION_INVOKE_METHODS,
35407
37257
  FUNCTION_INVOKE_MAX_RESPONSE_BYTES,
@@ -35439,6 +37289,8 @@ export {
35439
37289
  POLICY_RULES,
35440
37290
  POLICY_ACTIONS,
35441
37291
  NaroClient,
37292
+ dashboardCapability,
37293
+ dashboardLink,
35442
37294
  BOOKMARK_FORMAT_MESSAGE,
35443
37295
  isBookmark,
35444
37296
  RESTORE_OUTCOME_UNKNOWN_CODES,