reffy-cli 1.4.2 → 1.7.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.
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import { archivePlanningChange } from "./plan-archive.js";
10
10
  import { createPlanScaffold } from "./plan.js";
11
11
  import { DEFAULT_PLANNING_RELATIVE_DIR, looksLikePlanningDir, resolveCanonicalPlanningPath } from "./planning-paths.js";
12
12
  import { listPlanningChanges, showPlanningChange, validatePlanningChange } from "./plan-runtime.js";
13
- import { assertRemoteIdentity, collectWorkspaceDocuments, describeRemoteLinkage, ensureRemoteInit, extractWorkspaceIdentity, mergeRemoteConfig, PaseoRemoteClient, readRemoteConfig, requireWorkspaceIdentity, resolveRemoteConfigPath, updateRemoteConfigMetadata, validateImportResult, } from "./remote.js";
13
+ import { assertWorkspaceSummaryIdentity, collectWorkspaceDocuments, describeRemoteLinkage, ensureManagerInit, ensureWorkspaceTarget, extractWorkspaceSummaryIdentity, PaseoManagerClient, PaseoWorkspaceBackendClient, readRemoteConfig, RemoteHttpError, removeWorkspaceTarget, requireWorkspaceIdentity, resolveRemoteConfigPath, resolveRemoteTarget, resolveSelectedWorkspaceId, updateRemoteConfigMetadata, validateImportResult, } from "./remote.js";
14
14
  import { DEFAULT_REFS_DIRNAME, detectWorkspaceState, looksLikeRefsDir } from "./refs-paths.js";
15
15
  import { prepareCanonicalPlanningLayout } from "./planning-workspace.js";
16
16
  import { ReferencesStore } from "./storage.js";
@@ -485,17 +485,29 @@ function planUsage() {
485
485
  function remoteUsage() {
486
486
  return [
487
487
  "Usage:",
488
- " reffy remote init [--repo PATH] [--output text|json] [--endpoint URL] [--pod-name NAME] [--actor-id ID] [--provision] [--env-file PATH]",
489
- " reffy remote status [--repo PATH] [--output text|json] [--endpoint URL] [--pod-name NAME] [--actor-id ID] [--env-file PATH]",
490
- " reffy remote push [--repo PATH] [--output text|json] [--endpoint URL] [--pod-name NAME] [--actor-id ID] [--env-file PATH]",
491
- " reffy remote ls [--repo PATH] [--output text|json] [--prefix PATH] [--endpoint URL] [--pod-name NAME] [--actor-id ID] [--env-file PATH]",
492
- " reffy remote cat <path> [--repo PATH] [--output text|json] [--endpoint URL] [--pod-name NAME] [--actor-id ID] [--env-file PATH]",
488
+ " reffy remote init [--repo PATH] [--output text|json] [--workspace-id ID] [--endpoint URL] [--manager-pod NAME] [--manager-actor ID] [--provision] [--create|--resolve] [--label TEXT] [--env-file PATH]",
489
+ " reffy remote status [--repo PATH] [--output text|json] [--workspace-id ID] [--project-id ID] [--env-file PATH]",
490
+ " reffy remote push [--repo PATH] [--output text|json] [--workspace-id ID] [--env-file PATH]",
491
+ " reffy remote ls [--repo PATH] [--output text|json] [--workspace-id ID] [--project-id ID] [--prefix PATH] [--env-file PATH]",
492
+ " reffy remote cat <path> [--repo PATH] [--output text|json] [--workspace-id ID] [--project-id ID] [--env-file PATH]",
493
+ " reffy remote snapshot [--repo PATH] [--output text|json] [--workspace-id ID] [--project-id ID] [--env-file PATH]",
494
+ " reffy remote workspace create <workspace-id> [--repo PATH] [--output text|json] [--label TEXT] [--env-file PATH]",
495
+ " reffy remote workspace get <workspace-id> [--repo PATH] [--output text|json] [--env-file PATH]",
496
+ " reffy remote workspace delete <workspace-id> --yes [--repo PATH] [--output text|json] [--env-file PATH]",
497
+ " reffy remote project register [--repo PATH] [--output text|json] [--workspace-id ID] [--project-id ID] [--env-file PATH]",
498
+ " reffy remote project list [--repo PATH] [--output text|json] [--workspace-id ID] [--env-file PATH]",
493
499
  "",
494
500
  "Options:",
501
+ " --workspace-id ID Selected workspace projection. Required when the manifest has multiple workspace_ids; otherwise inferred.",
502
+ " --project-id ID Override the local project_id (defaults to manifest.project_id) when reading another contributor's documents or registration.",
495
503
  " --endpoint URL Paseo base URL. Defaults to saved config or PASEO_ENDPOINT/.env.",
496
- " --pod-name NAME Paseo pod name. Defaults to saved config or PASEO_POD_NAME/.env.",
497
- " --actor-id ID Paseo actor id. Defaults to saved config or PASEO_ACTOR_ID/.env.",
498
- " --provision Create missing pod/actor during `remote init`.",
504
+ " --manager-pod Paseo pod hosting the workspace manager actor. Defaults to saved config or PASEO_MANAGER_POD/.env.",
505
+ " --manager-actor reffyWorkspaceManager.v1 actor id. Defaults to saved config or PASEO_MANAGER_ACTOR/.env.",
506
+ " --provision During `remote init`, create a fresh pod and manager actor when one is not configured.",
507
+ " --create During `remote init`, force-create the workspace through the manager (recoverable via 409).",
508
+ " --resolve During `remote init`, resolve an existing workspace through the manager and fail if it does not exist.",
509
+ " --label TEXT Optional human-readable workspace label passed to `workspace create`.",
510
+ " --yes Confirm a destructive operation (required for `workspace delete`).",
499
511
  " --prefix PATH Prefix filter for `remote ls`.",
500
512
  " --env-file PATH Dotenv file to load before resolving env-backed options. Defaults to .env.",
501
513
  ].join("\n");
@@ -676,70 +688,69 @@ function parsePlanArgs(argv) {
676
688
  }
677
689
  return args;
678
690
  }
691
+ const REMOTE_VALUE_FLAGS = new Set([
692
+ "--repo",
693
+ "--output",
694
+ "--endpoint",
695
+ "--manager-pod",
696
+ "--manager-actor",
697
+ "--env-file",
698
+ "--prefix",
699
+ "--workspace-id",
700
+ "--project-id",
701
+ "--label",
702
+ ]);
703
+ const REMOTE_BOOLEAN_FLAGS = new Set(["--json", "--provision", "--create", "--resolve", "--yes"]);
679
704
  function parseRemoteArgs(argv) {
680
705
  const repoRoot = parseRepoArg(argv);
681
706
  const args = {
682
707
  repoRoot,
683
708
  provision: false,
709
+ create: false,
710
+ resolve: false,
711
+ yes: false,
712
+ };
713
+ const setStr = (flag, value) => {
714
+ if (!value)
715
+ throw new Error(`${flag} requires a value`);
716
+ return value;
684
717
  };
685
718
  for (let i = 0; i < argv.length; i += 1) {
686
719
  const arg = argv[i];
687
- if (arg === "--repo") {
720
+ if (arg === "--repo" || arg === "--output") {
688
721
  i += 1;
689
722
  continue;
690
723
  }
691
- if (arg.startsWith("--repo="))
724
+ if (arg.startsWith("--repo=") || arg.startsWith("--output="))
692
725
  continue;
693
726
  if (arg === "--json")
694
727
  continue;
695
- if (arg === "--output") {
696
- i += 1;
697
- continue;
698
- }
699
- if (arg.startsWith("--output="))
700
- continue;
701
728
  if (arg === "--endpoint") {
702
- const value = argv[i + 1];
703
- if (!value)
704
- throw new Error("--endpoint requires a value");
705
- args.endpoint = value;
706
- i += 1;
729
+ args.endpoint = setStr(arg, argv[++i]);
707
730
  continue;
708
731
  }
709
732
  if (arg.startsWith("--endpoint=")) {
710
733
  args.endpoint = arg.split("=", 2)[1];
711
734
  continue;
712
735
  }
713
- if (arg === "--pod-name") {
714
- const value = argv[i + 1];
715
- if (!value)
716
- throw new Error("--pod-name requires a value");
717
- args.podName = value;
718
- i += 1;
736
+ if (arg === "--manager-pod") {
737
+ args.managerPod = setStr(arg, argv[++i]);
719
738
  continue;
720
739
  }
721
- if (arg.startsWith("--pod-name=")) {
722
- args.podName = arg.split("=", 2)[1];
740
+ if (arg.startsWith("--manager-pod=")) {
741
+ args.managerPod = arg.split("=", 2)[1];
723
742
  continue;
724
743
  }
725
- if (arg === "--actor-id") {
726
- const value = argv[i + 1];
727
- if (!value)
728
- throw new Error("--actor-id requires a value");
729
- args.actorId = value;
730
- i += 1;
744
+ if (arg === "--manager-actor") {
745
+ args.managerActor = setStr(arg, argv[++i]);
731
746
  continue;
732
747
  }
733
- if (arg.startsWith("--actor-id=")) {
734
- args.actorId = arg.split("=", 2)[1];
748
+ if (arg.startsWith("--manager-actor=")) {
749
+ args.managerActor = arg.split("=", 2)[1];
735
750
  continue;
736
751
  }
737
752
  if (arg === "--env-file") {
738
- const value = argv[i + 1];
739
- if (!value)
740
- throw new Error("--env-file requires a value");
741
- args.envFile = value;
742
- i += 1;
753
+ args.envFile = setStr(arg, argv[++i]);
743
754
  continue;
744
755
  }
745
756
  if (arg.startsWith("--env-file=")) {
@@ -747,52 +758,74 @@ function parseRemoteArgs(argv) {
747
758
  continue;
748
759
  }
749
760
  if (arg === "--prefix") {
750
- const value = argv[i + 1];
751
- if (!value)
752
- throw new Error("--prefix requires a value");
753
- args.prefix = value;
754
- i += 1;
761
+ args.prefix = setStr(arg, argv[++i]);
755
762
  continue;
756
763
  }
757
764
  if (arg.startsWith("--prefix=")) {
758
765
  args.prefix = arg.split("=", 2)[1];
759
766
  continue;
760
767
  }
768
+ if (arg === "--workspace-id") {
769
+ args.workspaceId = setStr(arg, argv[++i]);
770
+ continue;
771
+ }
772
+ if (arg.startsWith("--workspace-id=")) {
773
+ args.workspaceId = arg.split("=", 2)[1];
774
+ continue;
775
+ }
776
+ if (arg === "--project-id") {
777
+ args.projectId = setStr(arg, argv[++i]);
778
+ continue;
779
+ }
780
+ if (arg.startsWith("--project-id=")) {
781
+ args.projectId = arg.split("=", 2)[1];
782
+ continue;
783
+ }
784
+ if (arg === "--label") {
785
+ args.label = setStr(arg, argv[++i]);
786
+ continue;
787
+ }
788
+ if (arg.startsWith("--label=")) {
789
+ args.label = arg.split("=", 2)[1];
790
+ continue;
791
+ }
761
792
  if (arg === "--provision") {
762
793
  args.provision = true;
763
794
  continue;
764
795
  }
796
+ if (arg === "--create") {
797
+ args.create = true;
798
+ continue;
799
+ }
800
+ if (arg === "--resolve") {
801
+ args.resolve = true;
802
+ continue;
803
+ }
804
+ if (arg === "--yes") {
805
+ args.yes = true;
806
+ continue;
807
+ }
765
808
  if (arg.startsWith("--")) {
766
809
  throw new Error(`Unknown remote option: ${arg}`);
767
810
  }
768
811
  }
812
+ if (args.create && args.resolve) {
813
+ throw new Error("--create and --resolve are mutually exclusive");
814
+ }
769
815
  return args;
770
816
  }
771
817
  function getRemotePositionalArgs(argv) {
772
818
  const positionals = [];
773
819
  for (let i = 0; i < argv.length; i += 1) {
774
820
  const arg = argv[i];
775
- if (arg === "--repo" ||
776
- arg === "--output" ||
777
- arg === "--endpoint" ||
778
- arg === "--pod-name" ||
779
- arg === "--actor-id" ||
780
- arg === "--env-file" ||
781
- arg === "--prefix") {
821
+ if (REMOTE_VALUE_FLAGS.has(arg)) {
782
822
  i += 1;
783
823
  continue;
784
824
  }
785
- if (arg.startsWith("--repo=") ||
786
- arg.startsWith("--output=") ||
787
- arg.startsWith("--endpoint=") ||
788
- arg.startsWith("--pod-name=") ||
789
- arg.startsWith("--actor-id=") ||
790
- arg.startsWith("--env-file=") ||
791
- arg.startsWith("--prefix=") ||
792
- arg === "--json" ||
793
- arg === "--provision") {
825
+ if (Array.from(REMOTE_VALUE_FLAGS).some((flag) => arg.startsWith(`${flag}=`)))
826
+ continue;
827
+ if (REMOTE_BOOLEAN_FLAGS.has(arg))
794
828
  continue;
795
- }
796
829
  if (arg.startsWith("--"))
797
830
  continue;
798
831
  positionals.push(arg);
@@ -809,30 +842,267 @@ function printSection(title, values) {
809
842
  console.log(`- ${value}`);
810
843
  }
811
844
  }
812
- async function resolveRemoteContext(args) {
845
+ async function resolveRemoteContext(args, opts = {}) {
813
846
  await loadDotEnvIfPresent(args.repoRoot, args.envFile ?? ".env");
814
847
  const store = new ReferencesStore(args.repoRoot);
815
- const identity = requireWorkspaceIdentity(await store.getWorkspaceIdentity());
816
- const savedConfig = await readRemoteConfig(args.repoRoot);
817
- const overrideValues = {
818
- endpoint: args.endpoint ?? process.env.PASEO_ENDPOINT,
819
- pod_name: args.podName ?? process.env.PASEO_POD_NAME,
820
- actor_id: args.actorId ?? process.env.PASEO_ACTOR_ID,
821
- };
822
- const mergedConfig = mergeRemoteConfig(savedConfig, overrideValues);
823
- const hasOverrides = Boolean(overrideValues.endpoint || overrideValues.pod_name || overrideValues.actor_id);
824
- const linkageSource = mergedConfig
825
- ? savedConfig && !hasOverrides
826
- ? "saved_config"
827
- : "cli_or_env_overrides"
828
- : "unconfigured";
848
+ const identityInfo = await store.getWorkspaceIdentity();
849
+ const workspaceId = resolveSelectedWorkspaceId(identityInfo, args.workspaceId);
850
+ const identity = requireWorkspaceIdentity(identityInfo, workspaceId);
851
+ const projectId = (args.projectId ?? identity.project_id).trim();
852
+ if (!projectId) {
853
+ throw new Error("Could not resolve a project id; pass --project-id or define manifest.project_id");
854
+ }
855
+ let savedConfig = null;
856
+ let replacedLegacy = false;
857
+ try {
858
+ savedConfig = await readRemoteConfig(args.repoRoot);
859
+ }
860
+ catch (error) {
861
+ const message = error instanceof Error ? error.message : String(error);
862
+ if (opts.tolerateLegacyConfig &&
863
+ (message.includes("legacy v1 single-target shape") ||
864
+ message.includes("legacy v2 combined-backend shape") ||
865
+ message.includes("legacy v3 shape"))) {
866
+ replacedLegacy = true;
867
+ }
868
+ else {
869
+ throw error;
870
+ }
871
+ }
872
+ const endpointForResolve = (args.endpoint ?? process.env.PASEO_ENDPOINT ?? "").trim();
873
+ const resolvedTarget = endpointForResolve
874
+ ? resolveRemoteTarget(savedConfig, workspaceId, endpointForResolve)
875
+ : null;
829
876
  return {
830
877
  store,
831
878
  identity,
879
+ workspaceId,
880
+ projectId,
832
881
  configPath: resolveRemoteConfigPath(args.repoRoot),
833
882
  savedConfig,
834
- mergedConfig,
835
- linkageSource,
883
+ resolvedTarget,
884
+ replacedLegacy,
885
+ };
886
+ }
887
+ function resolvePaseoEndpoint(args) {
888
+ const endpoint = (args.endpoint ?? process.env.PASEO_ENDPOINT ?? "").trim();
889
+ if (!endpoint) {
890
+ throw new Error("PASEO_ENDPOINT is required for remote commands. Add it to your .env (or export it in your shell) and retry. The endpoint is no longer persisted in .reffy/state/remote.json.");
891
+ }
892
+ return endpoint;
893
+ }
894
+ function resolvePaseoToken(opts) {
895
+ const token = process.env.PASEO_TOKEN?.trim() || undefined;
896
+ if (opts.required && !token) {
897
+ throw new Error("PASEO_TOKEN is required for remote commands. It is the bearer token returned by `reffy remote init --provision`; copy it from your team secret store into your .env (or export it). The CLI never persists this token to disk.");
898
+ }
899
+ return token;
900
+ }
901
+ function findRemoteHttpError(error) {
902
+ if (error instanceof RemoteHttpError)
903
+ return error;
904
+ if (error instanceof Error && error.cause)
905
+ return findRemoteHttpError(error.cause);
906
+ return null;
907
+ }
908
+ function decorateRemoteError(error, fallback) {
909
+ const httpError = findRemoteHttpError(error);
910
+ if (!httpError)
911
+ return fallback;
912
+ // Inspect the request URL to decide which hint applies. The path shape
913
+ // unambiguously tells us whether the call was a manager workspace lookup,
914
+ // a manager project registration, or a workspace-backend per-project read.
915
+ let pathname = "";
916
+ try {
917
+ pathname = new URL(httpError.url).pathname;
918
+ }
919
+ catch {
920
+ pathname = httpError.url;
921
+ }
922
+ const projectRouteMatch = /\/projects\/[^/]+(?:\/|$)/.test(pathname);
923
+ const workspacesRouteMatch = /\/workspaces\/[^/]+(?:\/|$)/.test(pathname);
924
+ const backendProjectMatch = /\/workspace\/projects\/[^/]+/.test(pathname);
925
+ if (httpError.status === 401) {
926
+ return `${fallback}\nAuthorization rejected by Paseo. Check that PASEO_TOKEN matches the current manager token in your team secret store, and that it is exported into the shell running reffy.`;
927
+ }
928
+ if (httpError.status === 404) {
929
+ if (backendProjectMatch || (projectRouteMatch && workspacesRouteMatch)) {
930
+ return `${fallback}\nThe project is not registered for this workspace. Run \`reffy remote project register\` (or \`reffy remote init\`) to register, then retry.`;
931
+ }
932
+ if (workspacesRouteMatch) {
933
+ return `${fallback}\nThe workspace was not found on the manager. Run \`reffy remote workspace create <workspace-id>\` to provision it, then retry.`;
934
+ }
935
+ }
936
+ return fallback;
937
+ }
938
+ async function handleRemoteWorkspaceSubcommand(parsed, remoteArgs, output, reportError) {
939
+ const positionals = getRemotePositionalArgs(remoteArgs);
940
+ // positionals[0] is the workspace subcommand verb (create|get); positionals[1] is the workspace id
941
+ const verb = positionals[0];
942
+ const workspaceIdArg = positionals[1];
943
+ if (!verb || (verb !== "create" && verb !== "get" && verb !== "delete")) {
944
+ console.error("reffy remote workspace requires a subcommand: create | get | delete");
945
+ console.error(remoteUsage());
946
+ return 1;
947
+ }
948
+ if (!workspaceIdArg) {
949
+ console.error(`reffy remote workspace ${verb} requires a workspace id`);
950
+ return 1;
951
+ }
952
+ try {
953
+ await loadDotEnvIfPresent(parsed.repoRoot, parsed.envFile ?? ".env");
954
+ const endpoint = resolvePaseoEndpoint(parsed);
955
+ const token = resolvePaseoToken({ required: true });
956
+ const savedConfig = await readRemoteConfig(parsed.repoRoot);
957
+ if (!savedConfig) {
958
+ throw new Error("Remote linkage is not initialized. Run `reffy remote init --provision` (or with --manager-pod/--manager-actor) first.");
959
+ }
960
+ if (verb === "delete") {
961
+ if (!parsed.yes) {
962
+ throw new Error(`Refusing to delete workspace "${workspaceIdArg}" without --yes. Workspace deletion is destructive: it removes the shared workspace and all contributions stored under it on the manager. Re-run with --yes to confirm.`);
963
+ }
964
+ const manager = new PaseoManagerClient(endpoint, savedConfig.manager, token);
965
+ const result = await manager.deleteWorkspace(workspaceIdArg);
966
+ const removal = await removeWorkspaceTarget(parsed.repoRoot, savedConfig, workspaceIdArg);
967
+ const payload = {
968
+ status: "ok",
969
+ command: "remote",
970
+ subcommand: "workspace delete",
971
+ workspace_id: workspaceIdArg,
972
+ manager: savedConfig.manager,
973
+ already_absent: result.alreadyAbsent,
974
+ local_target_removed: removal.existed,
975
+ configured_workspace_ids: Object.keys(removal.config.targets).sort(),
976
+ };
977
+ if (output === "json") {
978
+ printResult(output, payload);
979
+ }
980
+ else {
981
+ console.log(`Workspace delete: ${result.alreadyAbsent ? "already_absent" : "deleted"}`);
982
+ console.log(`Workspace id: ${workspaceIdArg}`);
983
+ console.log(`Local target removed: ${removal.existed ? "yes" : "no"}`);
984
+ console.log(`Configured workspace ids: ${payload.configured_workspace_ids.join(", ") || "(none)"}`);
985
+ }
986
+ return 0;
987
+ }
988
+ const result = await ensureWorkspaceTarget(parsed.repoRoot, savedConfig, {
989
+ workspaceId: workspaceIdArg,
990
+ mode: verb === "create" ? "create" : "resolve",
991
+ endpoint,
992
+ token,
993
+ metadata: parsed.label ? { label: parsed.label } : undefined,
994
+ });
995
+ const payload = {
996
+ status: "ok",
997
+ command: "remote",
998
+ subcommand: `workspace ${verb}`,
999
+ workspace_id: workspaceIdArg,
1000
+ manager: result.config.manager,
1001
+ workspace_backend: result.workspace_backend,
1002
+ outcome: result.outcome,
1003
+ };
1004
+ if (output === "json") {
1005
+ printResult(output, payload);
1006
+ }
1007
+ else {
1008
+ console.log(`Workspace ${verb}: ${result.outcome}`);
1009
+ console.log(`Workspace id: ${workspaceIdArg}`);
1010
+ console.log(`Workspace backend: ${result.workspace_backend.pod_name}/${result.workspace_backend.actor_id}`);
1011
+ }
1012
+ return 0;
1013
+ }
1014
+ catch (error) {
1015
+ return reportError(`workspace ${verb}`, error);
1016
+ }
1017
+ }
1018
+ async function handleRemoteProjectSubcommand(parsed, remoteArgs, output, reportError) {
1019
+ const positionals = getRemotePositionalArgs(remoteArgs);
1020
+ const verb = positionals[0];
1021
+ if (!verb || (verb !== "register" && verb !== "list")) {
1022
+ console.error("reffy remote project requires a subcommand: register | list");
1023
+ console.error(remoteUsage());
1024
+ return 1;
1025
+ }
1026
+ try {
1027
+ const context = await resolveRemoteContext(parsed);
1028
+ if (!context.savedConfig) {
1029
+ throw new Error("Remote linkage is not initialized. Run `reffy remote init --provision` (or with --manager-pod/--manager-actor) first.");
1030
+ }
1031
+ const endpoint = resolvePaseoEndpoint(parsed);
1032
+ const token = resolvePaseoToken({ required: true });
1033
+ const manager = new PaseoManagerClient(endpoint, context.savedConfig.manager, token);
1034
+ if (verb === "register") {
1035
+ const registration = await manager.registerProject(context.workspaceId, context.projectId);
1036
+ const payload = {
1037
+ status: "ok",
1038
+ command: "remote",
1039
+ subcommand: "project register",
1040
+ workspace_id: context.workspaceId,
1041
+ project_id: context.projectId,
1042
+ registration: registration.alreadyRegistered ? "already_registered" : "registered",
1043
+ };
1044
+ if (output === "json") {
1045
+ printResult(output, payload);
1046
+ }
1047
+ else {
1048
+ console.log(`Project registration: ${payload.registration}`);
1049
+ console.log(`Workspace id: ${payload.workspace_id}`);
1050
+ console.log(`Project id: ${payload.project_id}`);
1051
+ }
1052
+ return 0;
1053
+ }
1054
+ // list
1055
+ const list = await manager.listProjects(context.workspaceId);
1056
+ const projects = (list.projects ?? [])
1057
+ .map((p) => (typeof p.project_id === "string" ? p.project_id : ""))
1058
+ .filter(Boolean)
1059
+ .sort();
1060
+ const payload = {
1061
+ status: "ok",
1062
+ command: "remote",
1063
+ subcommand: "project list",
1064
+ workspace_id: context.workspaceId,
1065
+ projects,
1066
+ };
1067
+ if (output === "json") {
1068
+ printResult(output, payload);
1069
+ }
1070
+ else if (projects.length === 0) {
1071
+ console.log("(no projects registered)");
1072
+ }
1073
+ else {
1074
+ for (const id of projects)
1075
+ console.log(id);
1076
+ }
1077
+ return 0;
1078
+ }
1079
+ catch (error) {
1080
+ return reportError(`project ${verb}`, error);
1081
+ }
1082
+ }
1083
+ async function ensureResolvedTarget(args, context, runtime) {
1084
+ if (context.resolvedTarget) {
1085
+ return { target: context.resolvedTarget, recovered: false };
1086
+ }
1087
+ if (!context.savedConfig) {
1088
+ throw new Error(`Remote linkage is not configured. Run \`reffy remote init --workspace-id ${context.workspaceId}\` to configure manager identity and resolve the workspace backend. Expected config path: ${context.configPath}`);
1089
+ }
1090
+ // Manager identity is configured but workspace_backend identity is missing for this workspace.
1091
+ // Recover by resolving through the manager.
1092
+ const result = await ensureWorkspaceTarget(args.repoRoot, context.savedConfig, {
1093
+ workspaceId: context.workspaceId,
1094
+ mode: "resolve",
1095
+ endpoint: runtime.endpoint,
1096
+ token: runtime.token,
1097
+ });
1098
+ return {
1099
+ target: {
1100
+ endpoint: runtime.endpoint,
1101
+ manager: result.config.manager,
1102
+ workspace_backend: result.workspace_backend,
1103
+ last_imported_at: result.config.targets[context.workspaceId]?.last_imported_at,
1104
+ },
1105
+ recovered: true,
836
1106
  };
837
1107
  }
838
1108
  async function main() {
@@ -892,91 +1162,165 @@ async function main() {
892
1162
  }
893
1163
  const output = parseOutputMode(remoteArgs);
894
1164
  const parsed = parseRemoteArgs(remoteArgs);
1165
+ const reportError = (subcmd, error) => {
1166
+ const message = error instanceof Error ? error.message : String(error);
1167
+ const decorated = decorateRemoteError(error, message);
1168
+ if (output === "json") {
1169
+ printResult(output, { status: "error", command: "remote", subcommand: subcmd, error: decorated });
1170
+ }
1171
+ else {
1172
+ console.error(decorated);
1173
+ }
1174
+ return 1;
1175
+ };
895
1176
  try {
1177
+ if (subcommand === "workspace") {
1178
+ return await handleRemoteWorkspaceSubcommand(parsed, remoteArgs, output, reportError);
1179
+ }
1180
+ if (subcommand === "project") {
1181
+ return await handleRemoteProjectSubcommand(parsed, remoteArgs, output, reportError);
1182
+ }
896
1183
  if (subcommand === "init") {
897
- const { identity, configPath } = await resolveRemoteContext(parsed);
898
- const endpoint = parsed.endpoint ?? process.env.PASEO_ENDPOINT;
899
- if (!endpoint) {
900
- throw new Error("Remote init requires --endpoint or PASEO_ENDPOINT (optionally loaded from .env)");
901
- }
902
- const result = await ensureRemoteInit(parsed.repoRoot, {
1184
+ const { identity, workspaceId, projectId, configPath, savedConfig, replacedLegacy } = await resolveRemoteContext(parsed, { tolerateLegacyConfig: true });
1185
+ const endpoint = resolvePaseoEndpoint(parsed);
1186
+ // Token is required up front unless we're going to create a fresh manager actor
1187
+ // during this run (which is what mints it). That happens when no manager actor id
1188
+ // is configured anywhere and --provision is set.
1189
+ const willMintToken = parsed.provision &&
1190
+ !(parsed.managerActor ?? process.env.PASEO_MANAGER_ACTOR ?? savedConfig?.manager.actor_id);
1191
+ const tokenFromEnv = resolvePaseoToken({ required: !willMintToken });
1192
+ const managerInit = await ensureManagerInit(parsed.repoRoot, {
903
1193
  endpoint,
904
- podName: parsed.podName ?? process.env.PASEO_POD_NAME,
905
- actorId: parsed.actorId ?? process.env.PASEO_ACTOR_ID,
1194
+ managerPodName: parsed.managerPod ?? process.env.PASEO_MANAGER_POD,
1195
+ managerActorId: parsed.managerActor ?? process.env.PASEO_MANAGER_ACTOR,
906
1196
  provision: parsed.provision,
907
- identity,
1197
+ existingConfig: savedConfig,
908
1198
  });
1199
+ const effectiveToken = managerInit.manager_auth_token ?? tokenFromEnv;
1200
+ if (!effectiveToken) {
1201
+ throw new Error("Internal error: no bearer token available after manager init. Either --provision should have minted one or PASEO_TOKEN should have been required up front.");
1202
+ }
1203
+ const targetMode = parsed.create
1204
+ ? "create"
1205
+ : parsed.resolve
1206
+ ? "resolve"
1207
+ : "auto";
1208
+ const targetResult = await ensureWorkspaceTarget(parsed.repoRoot, managerInit.config, {
1209
+ workspaceId,
1210
+ mode: targetMode,
1211
+ endpoint,
1212
+ token: effectiveToken,
1213
+ metadata: parsed.label ? { label: parsed.label } : undefined,
1214
+ });
1215
+ const manager = new PaseoManagerClient(endpoint, targetResult.config.manager, effectiveToken);
1216
+ const registration = await manager.registerProject(workspaceId, projectId);
909
1217
  const gitignore = await ensureGitignoreEntries(parsed.repoRoot, [".reffy/state/"]);
1218
+ const tokenJustIssued = managerInit.manager_auth_token != null;
910
1219
  const payload = {
911
1220
  status: "ok",
912
1221
  command: "remote",
913
1222
  subcommand: "init",
914
- provider: result.config.provider,
1223
+ provider: targetResult.config.provider,
915
1224
  config_path: configPath,
916
- endpoint: result.config.endpoint,
917
- pod_name: result.config.pod_name,
918
- actor_id: result.config.actor_id,
919
- created_pod: result.created_pod,
920
- created_actor: result.created_actor,
921
- project_id: identity.project_id,
922
- workspace_name: identity.workspace_name,
923
- linkage_mode: result.created_pod || result.created_actor ? "provisioned" : "existing",
1225
+ endpoint,
1226
+ workspace_id: workspaceId,
1227
+ project_id: projectId,
1228
+ manager: targetResult.config.manager,
1229
+ workspace_backend: targetResult.workspace_backend,
1230
+ created_pod: managerInit.created_pod,
1231
+ created_manager_actor: managerInit.created_actor,
1232
+ workspace_outcome: targetResult.outcome,
1233
+ project_registration: registration.alreadyRegistered ? "already_registered" : "registered",
1234
+ configured_workspace_ids: Object.keys(targetResult.config.targets).sort(),
1235
+ replaced_legacy_config: replacedLegacy,
924
1236
  gitignore_path: gitignore.path,
925
1237
  gitignore_added: gitignore.added,
1238
+ // One-time issuance: present in this response only; never persisted to disk.
1239
+ manager_token: managerInit.manager_auth_token ?? null,
1240
+ manager_token_persisted: false,
926
1241
  };
927
1242
  if (output === "json") {
928
1243
  printResult(output, payload);
929
1244
  }
930
1245
  else {
1246
+ if (replacedLegacy) {
1247
+ console.log("Replacing legacy remote linkage with v4 (no endpoint, bearer-token auth)");
1248
+ }
931
1249
  console.log("Remote linkage ready");
932
1250
  console.log(`Config: ${payload.config_path}`);
933
- console.log(`Endpoint: ${payload.endpoint}`);
934
- console.log(`Pod: ${payload.pod_name}`);
935
- console.log(`Actor: ${payload.actor_id}`);
1251
+ console.log(`Endpoint: ${payload.endpoint} (sourced from env, not persisted)`);
1252
+ console.log(`Manager: ${payload.manager.pod_name}/${payload.manager.actor_id}`);
1253
+ console.log(`Workspace id: ${payload.workspace_id}`);
1254
+ console.log(`Workspace backend: ${payload.workspace_backend.pod_name}/${payload.workspace_backend.actor_id}`);
1255
+ console.log(`Workspace outcome: ${payload.workspace_outcome}`);
936
1256
  console.log(`Project identity: ${payload.project_id}`);
937
- console.log(`Workspace name: ${payload.workspace_name}`);
1257
+ console.log(`Project registration: ${payload.project_registration}`);
938
1258
  console.log(`Created pod: ${payload.created_pod ? "yes" : "no"}`);
939
- console.log(`Created actor: ${payload.created_actor ? "yes" : "no"}`);
940
- console.log(`Linkage mode: ${payload.linkage_mode}`);
1259
+ console.log(`Created manager actor: ${payload.created_manager_actor ? "yes" : "no"}`);
1260
+ console.log(`Configured workspace ids: ${payload.configured_workspace_ids.join(", ")}`);
941
1261
  if (payload.gitignore_added.length > 0) {
942
1262
  console.log(`Updated .gitignore: ${payload.gitignore_added.join(", ")}`);
943
1263
  }
1264
+ if (tokenJustIssued && payload.manager_token) {
1265
+ console.log("");
1266
+ console.log("=================== MANAGER BEARER TOKEN ===================");
1267
+ console.log("Save this now. It will not be shown again and is not stored on disk.");
1268
+ console.log("Add it to your team secret store and export PASEO_TOKEN before");
1269
+ console.log("running any further reffy remote commands in this or any other repo.");
1270
+ console.log("");
1271
+ console.log(` ${payload.manager_token}`);
1272
+ console.log("============================================================");
1273
+ }
944
1274
  }
945
1275
  return 0;
946
1276
  }
947
1277
  const context = await resolveRemoteContext(parsed);
948
- if (!context.mergedConfig) {
949
- throw new Error(`Remote linkage is not configured. Run \`reffy remote init\` or provide endpoint, pod name, and actor id. Expected config path: ${context.configPath}`);
950
- }
951
- const client = new PaseoRemoteClient(context.mergedConfig);
1278
+ const endpoint = resolvePaseoEndpoint(parsed);
1279
+ const token = resolvePaseoToken({ required: true });
1280
+ const { target: resolvedTarget, recovered } = await ensureResolvedTarget(parsed, context, {
1281
+ endpoint,
1282
+ token,
1283
+ });
1284
+ const backendClient = new PaseoWorkspaceBackendClient(resolvedTarget.endpoint, resolvedTarget.workspace_backend, token);
1285
+ const managerClient = new PaseoManagerClient(resolvedTarget.endpoint, resolvedTarget.manager, token);
1286
+ const linkageLabel = describeRemoteLinkage({
1287
+ endpoint: resolvedTarget.endpoint,
1288
+ manager: resolvedTarget.manager,
1289
+ workspace_backend: resolvedTarget.workspace_backend,
1290
+ workspace_id: context.workspaceId,
1291
+ });
952
1292
  if (subcommand === "status") {
953
1293
  let workspace;
954
1294
  try {
955
- workspace = await client.getWorkspace();
1295
+ workspace = await backendClient.getWorkspace();
956
1296
  }
957
1297
  catch (error) {
958
1298
  const message = error instanceof Error ? error.message : String(error);
959
- throw new Error(`Remote status failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
1299
+ throw new Error(`Remote status failed using ${linkageLabel}: ${message}`, { cause: error });
960
1300
  }
961
- assertRemoteIdentity(workspace, context.identity);
962
- const remoteIdentity = extractWorkspaceIdentity(workspace);
1301
+ assertWorkspaceSummaryIdentity(workspace, context.workspaceId);
1302
+ const summaryIdentity = extractWorkspaceSummaryIdentity(workspace);
963
1303
  const payload = {
964
1304
  status: "ok",
965
1305
  command: "remote",
966
1306
  subcommand: "status",
967
1307
  config_path: context.configPath,
968
- provider: context.mergedConfig.provider,
969
- endpoint: context.mergedConfig.endpoint,
970
- pod_name: context.mergedConfig.pod_name,
971
- actor_id: context.mergedConfig.actor_id,
972
- linkage_source: context.linkageSource,
1308
+ provider: "paseo",
1309
+ endpoint: resolvedTarget.endpoint,
1310
+ workspace_id: context.workspaceId,
1311
+ project_id: context.projectId,
1312
+ manager: resolvedTarget.manager,
1313
+ workspace_backend: resolvedTarget.workspace_backend,
1314
+ recovered_workspace_backend: recovered,
973
1315
  local_identity: context.identity,
974
1316
  remote_identity: {
975
- project_id: remoteIdentity.project_id,
976
- workspace_name: remoteIdentity.workspace_name,
1317
+ workspace_id: summaryIdentity.workspace_id,
1318
+ actor_type: summaryIdentity.actor_type,
1319
+ backend_version: summaryIdentity.backend_version,
977
1320
  },
978
- document_count: remoteIdentity.document_count,
979
- last_imported_at: context.savedConfig?.last_imported_at ?? null,
1321
+ document_count: summaryIdentity.document_count,
1322
+ registered_project_count: summaryIdentity.registered_project_count,
1323
+ last_imported_at: resolvedTarget.last_imported_at ?? null,
980
1324
  workspace,
981
1325
  };
982
1326
  if (output === "json") {
@@ -986,47 +1330,55 @@ async function main() {
986
1330
  console.log("Remote workspace reachable");
987
1331
  console.log(`Config: ${payload.config_path}`);
988
1332
  console.log(`Endpoint: ${payload.endpoint}`);
989
- console.log(`Pod: ${payload.pod_name}`);
990
- console.log(`Actor: ${payload.actor_id}`);
991
- console.log(`Linkage source: ${payload.linkage_source}`);
992
- console.log(`Local identity: ${payload.local_identity.project_id}/${payload.local_identity.workspace_name}`);
993
- console.log(`Remote identity: ${String(payload.remote_identity.project_id)}/${String(payload.remote_identity.workspace_name)}`);
1333
+ console.log(`Manager: ${payload.manager.pod_name}/${payload.manager.actor_id}`);
1334
+ console.log(`Workspace id: ${payload.workspace_id}`);
1335
+ console.log(`Workspace backend: ${payload.workspace_backend.pod_name}/${payload.workspace_backend.actor_id}`);
1336
+ if (recovered)
1337
+ console.log("Recovered workspace backend identity through the manager");
1338
+ console.log(`Local identity: ${payload.local_identity.project_id}/${payload.local_identity.workspace_id}`);
1339
+ console.log(`Remote identity: workspace=${String(payload.remote_identity.workspace_id)} (${String(payload.remote_identity.actor_type)} ${String(payload.remote_identity.backend_version)})`);
994
1340
  console.log(`Remote document count: ${String(payload.document_count ?? "unknown")}`);
1341
+ console.log(`Registered projects: ${String(payload.registered_project_count ?? "unknown")}`);
995
1342
  console.log(`Last imported at: ${String(payload.last_imported_at ?? "never")}`);
996
1343
  }
997
1344
  return 0;
998
1345
  }
999
1346
  if (subcommand === "push") {
1000
1347
  const documents = await collectWorkspaceDocuments(parsed.repoRoot);
1348
+ const registration = await managerClient.registerProject(context.workspaceId, context.projectId);
1001
1349
  let importResult;
1002
1350
  try {
1003
- importResult = validateImportResult(await client.importWorkspace(documents, true));
1351
+ importResult = validateImportResult(await backendClient.importProject(context.projectId, documents, true));
1004
1352
  }
1005
1353
  catch (error) {
1006
1354
  const message = error instanceof Error ? error.message : String(error);
1007
- throw new Error(`Remote push failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
1355
+ throw new Error(`Remote push failed using ${linkageLabel}: ${message}`, { cause: error });
1008
1356
  }
1009
1357
  let workspace;
1010
1358
  try {
1011
- workspace = await client.getWorkspace();
1359
+ workspace = await backendClient.getWorkspace();
1012
1360
  }
1013
1361
  catch (error) {
1014
1362
  const message = error instanceof Error ? error.message : String(error);
1015
- throw new Error(`Remote push could not verify remote state using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
1363
+ throw new Error(`Remote push could not verify remote state using ${linkageLabel}: ${message}`, { cause: error });
1016
1364
  }
1017
- assertRemoteIdentity(workspace, context.identity);
1365
+ assertWorkspaceSummaryIdentity(workspace, context.workspaceId);
1018
1366
  const importedAt = new Date().toISOString();
1019
- await updateRemoteConfigMetadata(parsed.repoRoot, { last_imported_at: importedAt });
1367
+ await updateRemoteConfigMetadata(parsed.repoRoot, context.workspaceId, {
1368
+ last_imported_at: importedAt,
1369
+ });
1370
+ const summary = extractWorkspaceSummaryIdentity(workspace);
1020
1371
  const payload = {
1021
1372
  status: "ok",
1022
1373
  command: "remote",
1023
1374
  subcommand: "push",
1024
- endpoint: context.mergedConfig.endpoint,
1025
- pod_name: context.mergedConfig.pod_name,
1026
- actor_id: context.mergedConfig.actor_id,
1027
- linkage_source: context.linkageSource,
1028
- project_id: context.identity.project_id,
1029
- workspace_name: context.identity.workspace_name,
1375
+ endpoint: resolvedTarget.endpoint,
1376
+ workspace_id: context.workspaceId,
1377
+ project_id: context.projectId,
1378
+ manager: resolvedTarget.manager,
1379
+ workspace_backend: resolvedTarget.workspace_backend,
1380
+ recovered_workspace_backend: recovered,
1381
+ project_registration: registration.alreadyRegistered ? "already_registered" : "registered",
1030
1382
  local_document_count: documents.length,
1031
1383
  imported: importResult.imported,
1032
1384
  created: importResult.created,
@@ -1034,7 +1386,7 @@ async function main() {
1034
1386
  deleted: importResult.deleted,
1035
1387
  replace_missing: true,
1036
1388
  last_imported_at: importedAt,
1037
- remote_document_count: extractWorkspaceIdentity(workspace).document_count,
1389
+ remote_document_count: summary.document_count,
1038
1390
  };
1039
1391
  if (output === "json") {
1040
1392
  printResult(output, payload);
@@ -1042,9 +1394,12 @@ async function main() {
1042
1394
  else {
1043
1395
  console.log("Remote push complete");
1044
1396
  console.log(`Endpoint: ${payload.endpoint}`);
1045
- console.log(`Pod: ${payload.pod_name}`);
1046
- console.log(`Actor: ${payload.actor_id}`);
1047
- console.log(`Linkage source: ${payload.linkage_source}`);
1397
+ console.log(`Workspace id: ${payload.workspace_id}`);
1398
+ console.log(`Project id: ${payload.project_id}`);
1399
+ console.log(`Project registration: ${payload.project_registration}`);
1400
+ console.log(`Workspace backend: ${payload.workspace_backend.pod_name}/${payload.workspace_backend.actor_id}`);
1401
+ if (recovered)
1402
+ console.log("Recovered workspace backend identity through the manager");
1048
1403
  console.log("Mode: remote reflects local (replace_missing=true)");
1049
1404
  console.log(`Local document count: ${String(payload.local_document_count)}`);
1050
1405
  console.log(`Imported: ${String(payload.imported)}`);
@@ -1057,15 +1412,15 @@ async function main() {
1057
1412
  return 0;
1058
1413
  }
1059
1414
  if (subcommand === "ls") {
1060
- let snapshot;
1415
+ let listing;
1061
1416
  try {
1062
- snapshot = await client.getSnapshot(parsed.prefix);
1417
+ listing = await backendClient.listProjectDocuments(context.projectId, { prefix: parsed.prefix });
1063
1418
  }
1064
1419
  catch (error) {
1065
1420
  const message = error instanceof Error ? error.message : String(error);
1066
- throw new Error(`Remote ls failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
1421
+ throw new Error(`Remote ls failed using ${linkageLabel}: ${message}`, { cause: error });
1067
1422
  }
1068
- const paths = (snapshot.documents ?? [])
1423
+ const paths = (listing.documents ?? [])
1069
1424
  .map((document) => (typeof document.path === "string" ? document.path : ""))
1070
1425
  .filter(Boolean)
1071
1426
  .sort();
@@ -1073,10 +1428,10 @@ async function main() {
1073
1428
  status: "ok",
1074
1429
  command: "remote",
1075
1430
  subcommand: "ls",
1076
- endpoint: context.mergedConfig.endpoint,
1077
- pod_name: context.mergedConfig.pod_name,
1078
- actor_id: context.mergedConfig.actor_id,
1079
- linkage_source: context.linkageSource,
1431
+ endpoint: resolvedTarget.endpoint,
1432
+ workspace_id: context.workspaceId,
1433
+ project_id: context.projectId,
1434
+ workspace_backend: resolvedTarget.workspace_backend,
1080
1435
  prefix: parsed.prefix ?? null,
1081
1436
  paths,
1082
1437
  };
@@ -1103,23 +1458,23 @@ async function main() {
1103
1458
  }
1104
1459
  let document;
1105
1460
  try {
1106
- document = await client.getDocument(documentPath);
1461
+ document = await backendClient.getProjectDocument(context.projectId, documentPath);
1107
1462
  }
1108
1463
  catch (error) {
1109
1464
  const message = error instanceof Error ? error.message : String(error);
1110
- throw new Error(`Remote cat failed using ${describeRemoteLinkage(context.mergedConfig)}: ${message}`);
1465
+ throw new Error(`Remote cat failed using ${linkageLabel}: ${message}`, { cause: error });
1111
1466
  }
1112
1467
  if (!document.document) {
1113
- throw new Error(`Remote document not found using ${describeRemoteLinkage(context.mergedConfig)}: ${documentPath}`);
1468
+ throw new Error(`Remote document not found using ${linkageLabel}: ${documentPath}`);
1114
1469
  }
1115
1470
  const payload = {
1116
1471
  status: "ok",
1117
1472
  command: "remote",
1118
1473
  subcommand: "cat",
1119
- endpoint: context.mergedConfig.endpoint,
1120
- pod_name: context.mergedConfig.pod_name,
1121
- actor_id: context.mergedConfig.actor_id,
1122
- linkage_source: context.linkageSource,
1474
+ endpoint: resolvedTarget.endpoint,
1475
+ workspace_id: context.workspaceId,
1476
+ project_id: context.projectId,
1477
+ workspace_backend: resolvedTarget.workspace_backend,
1123
1478
  document: document.document,
1124
1479
  };
1125
1480
  if (output === "json") {
@@ -1134,24 +1489,44 @@ async function main() {
1134
1489
  }
1135
1490
  return 0;
1136
1491
  }
1492
+ if (subcommand === "snapshot") {
1493
+ let snapshot;
1494
+ try {
1495
+ snapshot = await backendClient.getProjectSnapshot(context.projectId);
1496
+ }
1497
+ catch (error) {
1498
+ const message = error instanceof Error ? error.message : String(error);
1499
+ throw new Error(`Remote snapshot failed using ${linkageLabel}: ${message}`, { cause: error });
1500
+ }
1501
+ const payload = {
1502
+ status: "ok",
1503
+ command: "remote",
1504
+ subcommand: "snapshot",
1505
+ endpoint: resolvedTarget.endpoint,
1506
+ workspace_id: context.workspaceId,
1507
+ project_id: context.projectId,
1508
+ workspace_backend: resolvedTarget.workspace_backend,
1509
+ snapshot,
1510
+ };
1511
+ if (output === "json") {
1512
+ printResult(output, payload);
1513
+ }
1514
+ else {
1515
+ const docs = (snapshot.documents ?? []).map((d) => (typeof d.path === "string" ? d.path : "")).filter(Boolean);
1516
+ console.log(`Project snapshot for ${context.projectId} on workspace ${context.workspaceId}`);
1517
+ console.log(`Documents: ${String(docs.length)}`);
1518
+ for (const entry of docs.sort()) {
1519
+ console.log(`- ${entry}`);
1520
+ }
1521
+ }
1522
+ return 0;
1523
+ }
1137
1524
  console.error(`Unknown remote subcommand: ${subcommand}`);
1138
1525
  console.error(remoteUsage());
1139
1526
  return 1;
1140
1527
  }
1141
1528
  catch (error) {
1142
- const message = error instanceof Error ? error.message : String(error);
1143
- if (output === "json") {
1144
- printResult(output, {
1145
- status: "error",
1146
- command: "remote",
1147
- subcommand,
1148
- error: message,
1149
- });
1150
- }
1151
- else {
1152
- console.error(message);
1153
- }
1154
- return 1;
1529
+ return reportError(subcommand, error);
1155
1530
  }
1156
1531
  }
1157
1532
  if (command === "plan") {