bdy 1.25.6-dev → 1.25.7-dev

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.25.6-dev",
4
+ "version": "1.25.7-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -52,11 +52,13 @@ class CliCommand extends commander_1.Command {
52
52
  hideVersionUpdate;
53
53
  agentPort;
54
54
  skipPre = false;
55
+ usesDefaultContext = false;
55
56
  defaultErrorFormat = 'text';
56
57
  metadata;
57
58
  constructor(name, metadata = {}) {
58
59
  super(name);
59
60
  this.metadata = metadata;
61
+ this.option('--context <name>', texts_1.OPTION_CONTEXT);
60
62
  }
61
63
  addOption(option) {
62
64
  if (option.long === '--format' && !option.argChoices) {
@@ -30,10 +30,19 @@ const environment_1 = __importDefault(require("./command/environment"));
30
30
  const register_1 = __importDefault(require("./command/register"));
31
31
  const mcp_1 = __importDefault(require("./command/mcp"));
32
32
  const discover_1 = __importDefault(require("./command/discover"));
33
+ const context_1 = __importDefault(require("./command/context"));
34
+ const input_1 = __importDefault(require("./input"));
33
35
  const cliError_1 = __importDefault(require("./cliError"));
36
+ const cliCommand_1 = __importDefault(require("./cliCommand"));
34
37
  const texts_2 = require("./texts");
35
38
  function cliIndex() {
36
39
  const program = (0, utils_1.newCommand)('bdy', texts_1.DESC_PROGRAM);
40
+ program.hook('preAction', (_, command) => {
41
+ const options = command.optsWithGlobals();
42
+ input_1.default.configureContext(command instanceof cliCommand_1.default && command.usesDefaultContext
43
+ ? { ...options, context: 'default' }
44
+ : options);
45
+ });
37
46
  program.hook('preAction', pre_1.default);
38
47
  program.configureHelp({
39
48
  formatHelp: utils_1.formatHelp,
@@ -65,6 +74,7 @@ function cliIndex() {
65
74
  program.addCommand(api_1.default);
66
75
  program.addCommand(mcp_1.default);
67
76
  program.addCommand(discover_1.default);
77
+ program.addCommand(context_1.default);
68
78
  program.action((_, cmd) => {
69
79
  if (cmd.args.length > 0) {
70
80
  output_1.default.exitError(new cliError_1.default('unknown_command', (0, texts_1.ERR_COMMAND_NOT_FOUND)(cmd.args[0]), (0, texts_2.TXT_CLI_USAGE_HINT)('bdy')));
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../../utils");
7
+ const input_1 = __importDefault(require("../../input"));
8
+ const output_1 = __importDefault(require("../../output"));
9
+ const cfg_1 = __importDefault(require("../../tunnel/cfg"));
10
+ const cliError_1 = __importDefault(require("../../cliError"));
11
+ const texts_1 = require("../../texts");
12
+ const command = (0, utils_1.newCommand)('add', texts_1.DESC_CONTEXT_ADD, {
13
+ requiredScopes: [],
14
+ examples: texts_1.EXAMPLE_CONTEXT_ADD,
15
+ helpText: texts_1.HELP_CONTEXT_ADD,
16
+ });
17
+ command.skipPre = true;
18
+ command.argument('<name>', texts_1.OPTION_CONTEXT_NAME);
19
+ command.option('--format <text|json>', texts_1.OPTION_FORMAT);
20
+ command.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
21
+ command.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
22
+ command.option('--token <token>', texts_1.OPTION_CONTEXT_TOKEN);
23
+ command.option('--api <endpoint>', texts_1.OPTION_CONTEXT_API);
24
+ command.option('--from-link', texts_1.OPTION_CONTEXT_LINK);
25
+ command.option('--from-git', texts_1.OPTION_CONTEXT_GIT);
26
+ command.option('--remote <name>', texts_1.OPTION_CONTEXT_REMOTE);
27
+ command.action((name, options) => {
28
+ input_1.default.contextEditableName(name);
29
+ if (Object.hasOwn(cfg_1.default.getContexts(), name))
30
+ throw new cliError_1.default('usage_error', (0, texts_1.ERR_CONTEXT_EXISTS)(name));
31
+ const context = input_1.default.contextDefinition(options);
32
+ cfg_1.default.setContext(name, context);
33
+ if (options.format === 'json')
34
+ output_1.default.json({ name, source: context.source });
35
+ else
36
+ output_1.default.normal((0, texts_1.TXT_CONTEXT_SAVED)(name));
37
+ });
38
+ exports.default = command;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../../utils");
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const cfg_1 = __importDefault(require("../../tunnel/cfg"));
9
+ const texts_1 = require("../../texts");
10
+ const command = (0, utils_1.newCommand)('list', texts_1.DESC_CONTEXT_LIST, {
11
+ requiredScopes: [],
12
+ examples: texts_1.EXAMPLE_CONTEXT_LIST,
13
+ helpText: texts_1.HELP_CONTEXT_LIST,
14
+ });
15
+ command.skipPre = true;
16
+ command.alias('ls');
17
+ command.option('--format <text|json>', texts_1.OPTION_FORMAT);
18
+ command.action((options) => {
19
+ const builtins = ['default', 'git', 'link'];
20
+ const active = cfg_1.default.getActiveContext();
21
+ const values = [...builtins, ...Object.keys(cfg_1.default.getContexts()).sort()].map((name) => ({
22
+ name,
23
+ active: name === active,
24
+ builtin: builtins.includes(name),
25
+ source: cfg_1.default.getContext(name)?.source || 'default',
26
+ }));
27
+ if (options.format === 'json')
28
+ output_1.default.json(values);
29
+ else
30
+ for (const item of values)
31
+ output_1.default.normal(`${item.active ? '*' : ' '} ${item.name}`);
32
+ });
33
+ exports.default = command;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../../utils");
7
+ const input_1 = __importDefault(require("../../input"));
8
+ const output_1 = __importDefault(require("../../output"));
9
+ const cfg_1 = __importDefault(require("../../tunnel/cfg"));
10
+ const texts_1 = require("../../texts");
11
+ const command = (0, utils_1.newCommand)('remove', texts_1.DESC_CONTEXT_REMOVE, {
12
+ requiredScopes: [],
13
+ examples: texts_1.EXAMPLE_CONTEXT_REMOVE,
14
+ helpText: texts_1.HELP_CONTEXT_REMOVE,
15
+ });
16
+ command.skipPre = true;
17
+ command.alias('rm');
18
+ command.argument('<name>', texts_1.OPTION_CONTEXT_NAME);
19
+ command.option('--format <text|json>', texts_1.OPTION_FORMAT);
20
+ command.option('-f, --force', texts_1.OPTION_CONFIRM_FORCE);
21
+ command.action(async (name, options) => {
22
+ input_1.default.contextEditableName(name);
23
+ cfg_1.default.getContext(name);
24
+ const confirmed = options.force ||
25
+ (await output_1.default.confirm((0, texts_1.TXT_CONTEXT_REMOVE_CONFIRM)(name), options.format === 'json'));
26
+ if (confirmed)
27
+ cfg_1.default.removeContext(name);
28
+ if (options.format === 'json')
29
+ output_1.default.json({
30
+ removed: confirmed ? name : null,
31
+ active: cfg_1.default.getActiveContext(),
32
+ });
33
+ else if (confirmed)
34
+ output_1.default.normal((0, texts_1.TXT_CONTEXT_REMOVED)(name));
35
+ });
36
+ exports.default = command;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../../utils");
7
+ const input_1 = __importDefault(require("../../input"));
8
+ const output_1 = __importDefault(require("../../output"));
9
+ const texts_1 = require("../../texts");
10
+ const command = (0, utils_1.newCommand)('show', texts_1.DESC_CONTEXT_SHOW, {
11
+ requiredScopes: [],
12
+ examples: texts_1.EXAMPLE_CONTEXT_SHOW,
13
+ helpText: texts_1.HELP_CONTEXT_SHOW,
14
+ });
15
+ command.skipPre = true;
16
+ command.argument('[name]', texts_1.OPTION_CONTEXT_NAME);
17
+ command.option('--format <text|json>', texts_1.OPTION_FORMAT);
18
+ command.action((name, options, cmd) => {
19
+ if (name)
20
+ input_1.default.configureContext({ ...cmd.optsWithGlobals(), context: name });
21
+ const result = input_1.default.showContext();
22
+ if (options.format === 'json')
23
+ output_1.default.json(result);
24
+ else
25
+ output_1.default.object({
26
+ [texts_1.TXT_CONTEXT_LABEL]: result.name,
27
+ [texts_1.TXT_CONTEXT_SOURCE_LABEL]: result.source,
28
+ [texts_1.TXT_CONTEXT_WORKSPACE_LABEL]: `${result.workspace || '-'} (${result.sources.workspace || '-'})`,
29
+ [texts_1.TXT_CONTEXT_PROJECT_LABEL]: `${result.project || '-'} (${result.sources.project || '-'})`,
30
+ [texts_1.TXT_CONTEXT_API_LABEL]: `${result.api} (${result.sources.api})`,
31
+ [texts_1.TXT_CONTEXT_REMOTE_LABEL]: result.remote || '-',
32
+ [texts_1.TXT_CONTEXT_TOKEN_LABEL]: `${result.token.available ? texts_1.TXT_CONTEXT_TOKEN_AVAILABLE : texts_1.TXT_CONTEXT_TOKEN_MISSING} (${result.token.source || '-'})`,
33
+ });
34
+ });
35
+ exports.default = command;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../../utils");
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const cfg_1 = __importDefault(require("../../tunnel/cfg"));
9
+ const texts_1 = require("../../texts");
10
+ const command = (0, utils_1.newCommand)('use', texts_1.DESC_CONTEXT_USE, {
11
+ requiredScopes: [],
12
+ examples: texts_1.EXAMPLE_CONTEXT_USE,
13
+ helpText: texts_1.HELP_CONTEXT_USE,
14
+ });
15
+ command.skipPre = true;
16
+ command.argument('<name>', texts_1.OPTION_CONTEXT_NAME);
17
+ command.option('--format <text|json>', texts_1.OPTION_FORMAT);
18
+ command.action((name, options) => {
19
+ cfg_1.default.setActiveContext(name);
20
+ if (options.format === 'json')
21
+ output_1.default.json({ active: name });
22
+ else
23
+ output_1.default.normal((0, texts_1.TXT_CONTEXT_ACTIVE)(name));
24
+ });
25
+ exports.default = command;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("../utils");
7
+ const texts_1 = require("../texts");
8
+ const add_1 = __importDefault(require("./context/add"));
9
+ const list_1 = __importDefault(require("./context/list"));
10
+ const show_1 = __importDefault(require("./context/show"));
11
+ const use_1 = __importDefault(require("./context/use"));
12
+ const remove_1 = __importDefault(require("./context/remove"));
13
+ const commandContext = (0, utils_1.newCommand)('context', texts_1.DESC_CONTEXT, {
14
+ requiredScopes: [],
15
+ helpText: texts_1.HELP_CONTEXT,
16
+ });
17
+ commandContext.addCommand(add_1.default);
18
+ commandContext.addCommand(list_1.default);
19
+ commandContext.addCommand(show_1.default);
20
+ commandContext.addCommand(use_1.default);
21
+ commandContext.addCommand(remove_1.default);
22
+ exports.default = commandContext;
@@ -293,6 +293,7 @@ async function authorize(options, forceRegisterEmail, forceRegisterName) {
293
293
  }
294
294
  }
295
295
  const commandLogin = (0, utils_1.newCommand)('login', texts_1.DESC_COMMAND_LOGIN);
296
+ commandLogin.usesDefaultContext = true;
296
297
  commandLogin.alias('sign-in');
297
298
  commandLogin.option('--token <token>', texts_1.OPTION_REST_API_TOKEN);
298
299
  commandLogin.option('--api <url>', texts_1.OPTION_REST_API_ENDPOINT);
@@ -9,6 +9,7 @@ const texts_1 = require("../texts");
9
9
  const utils_1 = require("../utils");
10
10
  const input_1 = __importDefault(require("../input"));
11
11
  const commandLogout = (0, utils_1.newCommand)('logout', texts_1.DESC_COMMAND_LOGOUT);
12
+ commandLogout.usesDefaultContext = true;
12
13
  const tryRemovingApp = async () => {
13
14
  try {
14
15
  const clientId = cfg_1.default.getApiClientId();
@@ -10,6 +10,7 @@ const output_1 = __importDefault(require("../../output"));
10
10
  const node_path_1 = require("node:path");
11
11
  const node_fs_1 = __importDefault(require("node:fs"));
12
12
  const commandProjectLink = (0, utils_1.newCommand)('link', texts_1.DESC_COMMAND_PROJECT_LINK);
13
+ commandProjectLink.usesDefaultContext = true;
13
14
  commandProjectLink.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
14
15
  commandProjectLink.option('-r, --relink', texts_1.OPT_COMMAND_PROJECT_LINK_RELINK);
15
16
  commandProjectLink.option('-g, --git', texts_1.OPT_COMMAND_PROJECT_LINK_GIT);
@@ -8,6 +8,7 @@ const texts_1 = require("../texts");
8
8
  const output_1 = __importDefault(require("../output"));
9
9
  const login_1 = require("./login");
10
10
  const commandRegister = (0, utils_1.newCommand)('register', texts_1.DESC_COMMAND_REGISTER);
11
+ commandRegister.usesDefaultContext = true;
11
12
  commandRegister.alias('sign-up');
12
13
  commandRegister.option('--api <url>', texts_1.OPTION_REST_API_ENDPOINT);
13
14
  commandRegister.option('--region <region>', texts_1.OPTION_REST_API_REGION);
@@ -5,16 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const utils_1 = require("../utils");
7
7
  const texts_1 = require("../texts");
8
- const cfg_1 = __importDefault(require("../tunnel/cfg"));
9
8
  const output_1 = __importDefault(require("../output"));
10
9
  const input_1 = __importDefault(require("../input"));
11
- const cfg_2 = __importDefault(require("../project/cfg"));
10
+ const cfg_1 = __importDefault(require("../project/cfg"));
12
11
  const commandWhoami = (0, utils_1.newCommand)('whoami', texts_1.DESC_COMMAND_WHOAMI);
13
12
  commandWhoami.option('--format <text|json>', texts_1.OPTION_FORMAT);
14
13
  commandWhoami.action(async (options) => {
15
- const workspace = cfg_1.default.getWorkspace();
16
- const localWorkspace = cfg_2.default.getWorkspace();
17
- const localProject = cfg_2.default.getProject();
14
+ const { workspace, project, localWorkspace } = input_1.default.accountScope();
18
15
  const client = input_1.default.restApiTokenClient();
19
16
  const user = await client.getInvoker();
20
17
  if (!user) {
@@ -30,12 +27,12 @@ commandWhoami.action(async (options) => {
30
27
  else {
31
28
  obj.Workspace = !workspace ? texts_1.TXT_WHOAMI_NO_WORKSPACE : workspace;
32
29
  }
33
- if (localProject) {
34
- obj.Project = localProject;
30
+ if (project) {
31
+ obj.Project = project;
35
32
  }
36
- const vtSuite = cfg_2.default.getVtSuite();
37
- const utSuite = cfg_2.default.getUtSuite();
38
- const crawlSuite = cfg_2.default.getCrawlSuite();
33
+ const vtSuite = cfg_1.default.getVtSuite();
34
+ const utSuite = cfg_1.default.getUtSuite();
35
+ const crawlSuite = cfg_1.default.getCrawlSuite();
39
36
  if (vtSuite) {
40
37
  obj['Visual test suite'] = vtSuite;
41
38
  }
@@ -3,13 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const cfg_1 = __importDefault(require("../../tunnel/cfg"));
6
+ const input_1 = __importDefault(require("../../input"));
7
7
  const output_1 = __importDefault(require("../../output"));
8
8
  const texts_1 = require("../../texts");
9
9
  const utils_1 = require("../../utils");
10
10
  const commandWorkspaceGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_WORKSPACE_GET);
11
11
  commandWorkspaceGet.action(async () => {
12
- const workspace = cfg_1.default.getWorkspace();
12
+ const { workspace } = input_1.default.accountScope();
13
13
  if (!workspace) {
14
14
  output_1.default.exitError(texts_1.TXT_WORKSPACE_NONE);
15
15
  }
@@ -9,6 +9,7 @@ const texts_1 = require("../../texts");
9
9
  const utils_1 = require("../../utils");
10
10
  const input_1 = __importDefault(require("../../input"));
11
11
  const commandWorkspaceSet = (0, utils_1.newCommand)('set', texts_1.DESC_COMMAND_WORKSPACE_SET);
12
+ commandWorkspaceSet.usesDefaultContext = true;
12
13
  commandWorkspaceSet.argument('[workspace]', texts_1.ARG_COMMAND_WORKSPACE);
13
14
  commandWorkspaceSet.action(async (workspaceDomain) => {
14
15
  output_1.default.handleSignals();
@@ -36,6 +36,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ const cfg_1 = __importDefault(require("./project/cfg"));
40
+ const context_1 = require("./project/context");
39
41
  const cliError_1 = __importDefault(require("./cliError"));
40
42
  const output_1 = __importDefault(require("./output"));
41
43
  const netmask_1 = require("netmask");
@@ -48,10 +50,159 @@ const utils_1 = require("./utils");
48
50
  const texts_1 = require("./texts");
49
51
  const tunnel_1 = require("./types/tunnel");
50
52
  const node_path_1 = __importStar(require("node:path"));
51
- const cfg_1 = __importDefault(require("./tunnel/cfg"));
53
+ const cfg_2 = __importDefault(require("./tunnel/cfg"));
52
54
  const sandbox_1 = require("./types/sandbox");
53
55
  const distro_1 = require("./types/distro");
54
56
  class Input {
57
+ static contextOptions = {};
58
+ static resolvedContextScope;
59
+ static configureContext(options) {
60
+ this.contextOptions = options;
61
+ this.resolvedContextScope = undefined;
62
+ }
63
+ static contextName() {
64
+ return this.contextOptions.context || cfg_2.default.getActiveContext();
65
+ }
66
+ static context() {
67
+ return cfg_2.default.getContext(this.contextName());
68
+ }
69
+ static contextScope() {
70
+ const context = this.context();
71
+ if (!context)
72
+ return null;
73
+ this.resolvedContextScope ??= (0, context_1.resolveContextScope)(context, process.cwd());
74
+ return this.resolvedContextScope;
75
+ }
76
+ static contextEditableName(name) {
77
+ if (['default', 'git', 'link'].includes(name))
78
+ throw new cliError_1.default('usage_error', texts_1.ERR_CONTEXT_RESERVED);
79
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name))
80
+ throw new cliError_1.default('usage_error', texts_1.ERR_CONTEXT_NAME);
81
+ return name;
82
+ }
83
+ static contextApiEndpoint(api) {
84
+ try {
85
+ const url = new URL(api.includes('://') ? api : `https://${api}`);
86
+ if (url.protocol !== 'https:' ||
87
+ url.username ||
88
+ url.password ||
89
+ url.pathname !== '/' ||
90
+ url.search ||
91
+ url.hash)
92
+ throw new Error();
93
+ return url.origin;
94
+ }
95
+ catch {
96
+ throw new cliError_1.default('usage_error', texts_1.ERR_CONTEXT_API);
97
+ }
98
+ }
99
+ static contextDefinition(options) {
100
+ if ((options.fromLink && options.fromGit) ||
101
+ ((options.fromLink || options.fromGit) &&
102
+ (options.workspace !== undefined || options.project !== undefined)) ||
103
+ (options.remote !== undefined && !options.fromGit))
104
+ throw new cliError_1.default('usage_error', texts_1.ERR_CONTEXT_SOURCE);
105
+ if (options.project && !options.workspace)
106
+ throw new cliError_1.default('usage_error', texts_1.ERR_CONTEXT_PROJECT);
107
+ const api = options.api !== undefined
108
+ ? this.contextApiEndpoint(options.api)
109
+ : undefined;
110
+ return {
111
+ source: options.fromGit ? 'git' : options.fromLink ? 'link' : 'explicit',
112
+ ...(options.workspace ? { workspace: options.workspace } : {}),
113
+ ...(options.project ? { project: options.project } : {}),
114
+ ...(options.token ? { token: options.token } : {}),
115
+ ...(api ? { api } : {}),
116
+ ...(options.fromGit ? { remote: options.remote || 'origin' } : {}),
117
+ };
118
+ }
119
+ // Account display preserves the legacy distinction between global and linked
120
+ // workspace, while named contexts own their entire scope.
121
+ static accountScope() {
122
+ if (this.context())
123
+ return {
124
+ workspace: this.restApiWorkspace(undefined, true),
125
+ project: this.restApiProject(undefined, true),
126
+ localWorkspace: '',
127
+ };
128
+ return {
129
+ workspace: cfg_2.default.getWorkspace(),
130
+ project: cfg_1.default.getProject(),
131
+ localWorkspace: cfg_1.default.getWorkspace(),
132
+ };
133
+ }
134
+ static showContext() {
135
+ const context = this.context();
136
+ const workspace = this.restApiWorkspace(this.contextOptions.workspace, true);
137
+ const project = this.restApiProject(this.contextOptions.project, true);
138
+ const auth = this.restApiToken(true, this.contextOptions.token);
139
+ const api = this.restApiBaseUrl(this.contextOptions.api, this.contextOptions.region, auth.token).origin;
140
+ const inSandbox = (0, utils_1.isInSandbox)();
141
+ const tokenSource = this.contextOptions.token
142
+ ? 'flag'
143
+ : context?.token
144
+ ? 'context'
145
+ : !inSandbox && process.env.BUDDY_TOKEN
146
+ ? 'environment'
147
+ : cfg_2.default.getApiToken()
148
+ ? 'config'
149
+ : process.env.BUDDY_TOKEN
150
+ ? 'environment'
151
+ : null;
152
+ const workspaceSource = this.contextOptions.workspace
153
+ ? 'flag'
154
+ : context
155
+ ? workspace
156
+ ? context.source
157
+ : null
158
+ : !inSandbox && process.env.BUDDY_WORKSPACE
159
+ ? 'environment'
160
+ : cfg_1.default.getWorkspace()
161
+ ? 'link'
162
+ : cfg_2.default.getWorkspace()
163
+ ? 'config'
164
+ : process.env.BUDDY_WORKSPACE
165
+ ? 'environment'
166
+ : null;
167
+ const projectSource = !project
168
+ ? null
169
+ : this.contextOptions.project
170
+ ? 'flag'
171
+ : context
172
+ ? context.source
173
+ : process.env.BUDDY_PROJECT
174
+ ? 'environment'
175
+ : 'link';
176
+ const apiSource = this.contextOptions.api
177
+ ? 'flag'
178
+ : context?.api
179
+ ? 'context'
180
+ : this.restApiBaseUrlFromToken(auth.token)
181
+ ? 'token'
182
+ : process.env.BUDDY_API_ENDPOINT
183
+ ? 'environment'
184
+ : cfg_2.default.getBaseUrl()
185
+ ? 'config'
186
+ : this.contextOptions.region
187
+ ? 'flag'
188
+ : process.env.BUDDY_REGION
189
+ ? 'environment'
190
+ : 'default';
191
+ return {
192
+ name: this.contextName(),
193
+ source: context?.source || 'default',
194
+ workspace,
195
+ project,
196
+ api,
197
+ remote: context?.source === 'git' ? context.remote || 'origin' : null,
198
+ token: { available: !!auth.token, source: tokenSource },
199
+ sources: {
200
+ workspace: workspaceSource,
201
+ project: projectSource,
202
+ api: apiSource,
203
+ },
204
+ };
205
+ }
55
206
  static timeout(timeout) {
56
207
  const t = parseInt(timeout, 10);
57
208
  if (isNaN(t) || t <= 0 || t > 999) {
@@ -344,7 +495,7 @@ class Input {
344
495
  // Inside a sandbox, cfg (written by an explicit `bdy login`) takes precedence
345
496
  // over the sandbox-injected BUDDY_TOKEN env var. Outside a sandbox, env beats cfg
346
497
  const tokenEnv = process.env.BUDDY_TOKEN;
347
- let t = token;
498
+ let t = token || this.context()?.token || '';
348
499
  if (!t && !inSandbox && tokenEnv)
349
500
  t = tokenEnv;
350
501
  let rf = '';
@@ -352,11 +503,11 @@ class Input {
352
503
  let cs = '';
353
504
  let ct = '';
354
505
  if (!t) {
355
- t = cfg_1.default.getApiToken();
356
- rf = cfg_1.default.getApiRefreshApiToken();
357
- cid = cfg_1.default.getApiClientId();
358
- cs = cfg_1.default.getApiClientSecret();
359
- ct = cfg_1.default.getApiClientToken();
506
+ t = cfg_2.default.getApiToken();
507
+ rf = cfg_2.default.getApiRefreshApiToken();
508
+ cid = cfg_2.default.getApiClientId();
509
+ cs = cfg_2.default.getApiClientSecret();
510
+ ct = cfg_2.default.getApiClientToken();
360
511
  }
361
512
  if (!t && inSandbox && tokenEnv)
362
513
  t = tokenEnv;
@@ -869,13 +1020,13 @@ class Input {
869
1020
  return null;
870
1021
  }
871
1022
  static restApiBaseUrl(api = '', region = '', token = '') {
872
- let u = api;
1023
+ let u = api || this.context()?.api || '';
873
1024
  if (!u)
874
1025
  u = this.restApiBaseUrlFromToken(token);
875
1026
  if (!u)
876
1027
  u = process.env.BUDDY_API_ENDPOINT || null;
877
1028
  if (!u)
878
- u = cfg_1.default.getBaseUrl();
1029
+ u = cfg_2.default.getBaseUrl();
879
1030
  if (!u) {
880
1031
  const r = region || process.env.BUDDY_REGION;
881
1032
  if (r?.toLowerCase() === utils_1.REST_API_REGION.EU) {
@@ -907,18 +1058,25 @@ class Input {
907
1058
  return p;
908
1059
  }
909
1060
  static restApiWorkspace(workspace, allowNull, ignoreProjectConfig = false) {
1061
+ if (!ignoreProjectConfig && this.context()) {
1062
+ const value = workspace || this.contextScope()?.workspace;
1063
+ if (value)
1064
+ return value;
1065
+ if (allowNull)
1066
+ return null;
1067
+ throw new cliError_1.default('usage_error', texts_1.ERR_REST_API_WORKSPACE, texts_1.TXT_CONTEXT_WORKSPACE_HINT);
1068
+ }
910
1069
  // Inside a sandbox, cfg/project cfg (set by an explicit `bdy login` or .bdy/cfg.json)
911
1070
  // takes precedence over the sandbox-injected BUDDY_WORKSPACE env var.
912
1071
  // Outside a sandbox, env beats cfg.
913
- const ProjectCfg = require('./project/cfg').default;
914
1072
  let w = workspace;
915
1073
  const inSandbox = (0, utils_1.isInSandbox)();
916
1074
  if (!w && !inSandbox && process.env.BUDDY_WORKSPACE)
917
1075
  w = process.env.BUDDY_WORKSPACE;
918
1076
  if (!w && !ignoreProjectConfig)
919
- w = ProjectCfg.getWorkspace();
920
- if (!w)
921
1077
  w = cfg_1.default.getWorkspace();
1078
+ if (!w)
1079
+ w = cfg_2.default.getWorkspace();
922
1080
  if (!w && inSandbox && process.env.BUDDY_WORKSPACE)
923
1081
  w = process.env.BUDDY_WORKSPACE;
924
1082
  if (!w) {
@@ -1247,12 +1405,35 @@ class Input {
1247
1405
  return sandboxId;
1248
1406
  }
1249
1407
  static restApiProject(project, allowNull) {
1250
- const ProjectCfg = require('./project/cfg').default;
1408
+ const context = this.context();
1409
+ if (context) {
1410
+ let value = project;
1411
+ if (!value) {
1412
+ let scope;
1413
+ try {
1414
+ scope = this.contextScope();
1415
+ }
1416
+ catch (err) {
1417
+ // An explicit workspace is sufficient for workspace-scoped commands.
1418
+ // If the dynamic source is unavailable, none of its project can carry over.
1419
+ if (!this.contextOptions.workspace || context.source === 'explicit')
1420
+ throw err;
1421
+ }
1422
+ const changedWorkspace = this.contextOptions.workspace && this.contextOptions.workspace !== scope?.workspace;
1423
+ if (!changedWorkspace)
1424
+ value = scope?.project || '';
1425
+ }
1426
+ if (value)
1427
+ return value;
1428
+ if (allowNull)
1429
+ return null;
1430
+ throw new cliError_1.default('usage_error', texts_1.ERR_REST_API_PROJECT, texts_1.TXT_CONTEXT_PROJECT_HINT);
1431
+ }
1251
1432
  let p = process.env.BUDDY_PROJECT;
1252
1433
  if (project)
1253
1434
  p = project;
1254
1435
  if (!p)
1255
- p = ProjectCfg.getProject();
1436
+ p = cfg_1.default.getProject();
1256
1437
  if (!p) {
1257
1438
  if (allowNull)
1258
1439
  return null;
@@ -1330,14 +1511,14 @@ class Input {
1330
1511
  const workspace = Input.restApiWorkspace('', true);
1331
1512
  const project = Input.restApiProject('', true);
1332
1513
  let fromLogin = false;
1333
- if (!token && cfg_1.default.getApiToken() && workspace) {
1514
+ if (!token && (this.context() ? this.restApiToken(true).token : cfg_2.default.getApiToken()) && workspace) {
1334
1515
  const client = Input.restApiTokenClient();
1335
1516
  const result = await client.getTunnelToken(workspace, project);
1336
1517
  token = String(result?.token || '');
1337
1518
  fromLogin = true;
1338
1519
  }
1339
1520
  else if (!token) {
1340
- token = cfg_1.default.getToken();
1521
+ token = cfg_2.default.getToken();
1341
1522
  }
1342
1523
  if (!token)
1343
1524
  throw new Error(texts_1.ERR_TOKEN_NOT_PROVIDED);
@@ -1350,7 +1531,7 @@ class Input {
1350
1531
  throw new Error(texts_1.ERR_WRONG_TOKEN);
1351
1532
  }
1352
1533
  if (!fromLogin)
1353
- cfg_1.default.setToken(token);
1534
+ cfg_2.default.setToken(token);
1354
1535
  return {
1355
1536
  token,
1356
1537
  host: d.host,
@@ -1382,9 +1563,8 @@ class Input {
1382
1563
  const envToken = process.env.BUDDY_VT_TOKEN;
1383
1564
  if (envToken)
1384
1565
  return envToken;
1385
- const ProjectCfg = require('./project/cfg').default;
1386
- const suiteIdentifier = ProjectCfg.getVtSuite();
1387
- if (suiteIdentifier && (cfg_1.default.getApiToken() || process.env.BUDDY_TOKEN)) {
1566
+ const suiteIdentifier = cfg_1.default.getVtSuite();
1567
+ if (suiteIdentifier && this.restApiToken(true).token) {
1388
1568
  const workspace = Input.restApiWorkspace('', true);
1389
1569
  const project = Input.restApiProject('', true);
1390
1570
  if (workspace && project) {
@@ -1402,9 +1582,8 @@ class Input {
1402
1582
  const envToken = process.env.BUDDY_CRAWL_TOKEN;
1403
1583
  if (envToken)
1404
1584
  return envToken;
1405
- const ProjectCfg = require('./project/cfg').default;
1406
- const suiteIdentifier = ProjectCfg.getCrawlSuite();
1407
- if (suiteIdentifier && (cfg_1.default.getApiToken() || process.env.BUDDY_TOKEN)) {
1585
+ const suiteIdentifier = cfg_1.default.getCrawlSuite();
1586
+ if (suiteIdentifier && this.restApiToken(true).token) {
1408
1587
  const workspace = Input.restApiWorkspace('', true);
1409
1588
  const project = Input.restApiProject('', true);
1410
1589
  if (workspace && project) {
@@ -1422,9 +1601,8 @@ class Input {
1422
1601
  const envToken = process.env.BUDDY_UT_TOKEN;
1423
1602
  if (envToken)
1424
1603
  return envToken;
1425
- const ProjectCfg = require('./project/cfg').default;
1426
- const suiteIdentifier = ProjectCfg.getUtSuite();
1427
- if (suiteIdentifier && (cfg_1.default.getApiToken() || process.env.BUDDY_TOKEN)) {
1604
+ const suiteIdentifier = cfg_1.default.getUtSuite();
1605
+ if (suiteIdentifier && this.restApiToken(true).token) {
1428
1606
  const workspace = Input.restApiWorkspace('', true);
1429
1607
  const project = Input.restApiProject('', true);
1430
1608
  if (workspace && project) {
@@ -375,7 +375,7 @@ class Output {
375
375
  }
376
376
  });
377
377
  }
378
- static async confirm(message = 'Are you sure?') {
378
+ static async confirm(message = 'Are you sure?', quiet = false) {
379
379
  if (this.isStdInTTY()) {
380
380
  const prompts = require('@inquirer/prompts');
381
381
  try {
@@ -383,15 +383,17 @@ class Output {
383
383
  message,
384
384
  theme: this.getInquirerTheme(),
385
385
  default: false,
386
- });
386
+ }, quiet ? { output: process.stderr } : undefined);
387
387
  }
388
388
  catch {
389
389
  this.exitError(texts_1.ERR_CANCELLED);
390
390
  }
391
391
  }
392
- this.cyan('? ', false);
393
- this.label(message, false);
394
- this.cyan(` No`);
392
+ if (!quiet) {
393
+ this.cyan('? ', false);
394
+ this.label(message, false);
395
+ this.cyan(` No`);
396
+ }
395
397
  return false;
396
398
  }
397
399
  static async inputString(message, def, required, signal) {
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseBuddyRemote = parseBuddyRemote;
7
+ exports.resolveContextScope = resolveContextScope;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const cliError_1 = __importDefault(require("../cliError"));
12
+ const utils_1 = require("../utils");
13
+ const texts_1 = require("../texts");
14
+ function parseBuddyRemote(remote) {
15
+ try {
16
+ // Accept both standard URLs and Git's scp-like SSH syntax.
17
+ const value = remote.includes('://')
18
+ ? remote
19
+ : remote.replace(/^(?:[^@/:]+@)?([^/:]+):(.+)$/, 'ssh://$1/$2');
20
+ const url = new URL(value);
21
+ if (!['https:', 'ssh:'].includes(url.protocol) ||
22
+ !Object.values(utils_1.REST_API_ENDPOINT).some((endpoint) => (0, utils_1.getAppHostByApiBaseUrl)(new URL(`https://${endpoint}`)) ===
23
+ url.hostname) ||
24
+ url.search ||
25
+ url.hash)
26
+ throw new Error();
27
+ const parts = url.pathname
28
+ .replace(/\/$/, '')
29
+ .replace(/\.git$/, '')
30
+ .split('/')
31
+ .slice(1);
32
+ if (parts.length !== 2 ||
33
+ parts.some((part) => !/^[a-zA-Z0-9_-]+$/.test(part)))
34
+ throw new Error();
35
+ return { workspace: parts[0], project: parts[1] };
36
+ }
37
+ catch {
38
+ // Do not include the raw URL: remotes can contain credentials.
39
+ throw new cliError_1.default('git_remote_unsupported', texts_1.ERR_CONTEXT_GIT_URL, texts_1.TXT_CONTEXT_GIT_HINT);
40
+ }
41
+ }
42
+ function resolveContextScope(context, cwd) {
43
+ if (context.source === 'explicit')
44
+ return {
45
+ workspace: context.workspace || null,
46
+ project: context.project || null,
47
+ remote: null,
48
+ };
49
+ if (context.source === 'link') {
50
+ let data;
51
+ try {
52
+ data = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(cwd, '.bdy/project.json'), 'utf8'));
53
+ }
54
+ catch (err) {
55
+ const missing = err.code === 'ENOENT';
56
+ throw new cliError_1.default(missing ? 'project_link_not_found' : 'project_link_invalid', missing ? texts_1.ERR_CONTEXT_LINK_MISSING : texts_1.ERR_CONTEXT_LINK_INVALID, texts_1.TXT_CONTEXT_LINK_HINT);
57
+ }
58
+ if (typeof data?.workspace !== 'string' ||
59
+ !data.workspace.trim() ||
60
+ typeof data?.project !== 'string' ||
61
+ !data.project.trim())
62
+ throw new cliError_1.default('project_link_invalid', texts_1.ERR_CONTEXT_LINK_INVALID, texts_1.TXT_CONTEXT_LINK_HINT);
63
+ return {
64
+ workspace: data.workspace,
65
+ project: data.project,
66
+ remote: null,
67
+ };
68
+ }
69
+ const remote = context.remote || 'origin';
70
+ let url;
71
+ try {
72
+ url = (0, node_child_process_1.execFileSync)('git', ['remote', 'get-url', '--', remote], {
73
+ cwd,
74
+ encoding: 'utf8',
75
+ timeout: 3000,
76
+ stdio: ['ignore', 'pipe', 'pipe'],
77
+ // Git diagnostics are classified below; keep their language deterministic.
78
+ env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
79
+ }).trim();
80
+ }
81
+ catch (err) {
82
+ const failure = err;
83
+ if (failure.code === 'ENOENT')
84
+ throw new cliError_1.default('git_not_found', texts_1.ERR_CONTEXT_GIT_MISSING, texts_1.TXT_CONTEXT_GIT_HINT);
85
+ if (failure.code === 'ETIMEDOUT')
86
+ throw new cliError_1.default('timeout', texts_1.ERR_CONTEXT_GIT_TIMEOUT, texts_1.TXT_CONTEXT_GIT_HINT);
87
+ const stderr = String(failure.stderr || '');
88
+ if (failure.status === 128 && /fatal: not a git repository/i.test(stderr))
89
+ throw new cliError_1.default('git_repository_not_found', texts_1.ERR_CONTEXT_GIT_REPO, texts_1.TXT_CONTEXT_GIT_HINT);
90
+ if (failure.status === 2 && /error: No such remote /i.test(stderr))
91
+ throw new cliError_1.default('git_remote_not_found', (0, texts_1.ERR_CONTEXT_GIT_REMOTE)(remote), texts_1.TXT_CONTEXT_GIT_HINT);
92
+ // Neither permission errors nor an interrupted Git process imply a missing repo.
93
+ // Never forward raw diagnostics, which may contain remote credentials.
94
+ throw new cliError_1.default('command_failed', texts_1.ERR_CONTEXT_GIT_FAILED, texts_1.TXT_CONTEXT_GIT_HINT);
95
+ }
96
+ return { ...parseBuddyRemote(url), remote };
97
+ }
@@ -22,7 +22,8 @@ exports.DESC_COMMAND_ARTIFACT_GET = exports.TXT_ARTIFACT_DOCKER_LOGIN_FAILED = e
22
22
  exports.EXAMPLE_ROUTE_LIST = exports.EXAMPLE_ROUTE_UPDATE = exports.EXAMPLE_ROUTE_DELETE = exports.EXAMPLE_DISTRO_DELETE = exports.EXAMPLE_ROUTE_GET = exports.EXAMPLE_DISTRO_UPDATE = exports.EXAMPLE_DISTRO_GET = exports.EXAMPLE_DISTRO_LIST = exports.EXAMPLE_SANDBOX_CP = exports.ERR_PROJECT_NO_PROJECTS = exports.TXT_LOGIN_SELECT_PROJECT = exports.TXT_COMMAND_PROJECT_CREATED = exports.TXT_COMMAND_PROJECT_LINK_NOT_FOUND = exports.TXT_COMMAND_PROJECT_LINK_NAME = exports.TXT_COMMAND_PROJECT_LINK_SELECT_PROJECT = exports.TXT_COMMAND_PROJECT_LINK_TO = exports.TXT_COMMAND_PROJECT_LINK_EXISTING_DESC = exports.TXT_COMMAND_PROJECT_LINK_EXISTING = exports.TXT_COMMAND_PROJECT_LINK_CREATE_NEW_DESC = exports.TXT_COMMAND_PROJECT_LINK_CREATE_NEW = exports.ERR_COMMAND_PROJECT_LINK_DIR_NOT_DIR = exports.TXT_COMMAND_PROJECT_LINK_GIT_REPO_INITIALIZED = exports.TXT_COMMAND_PROJECT_LINK_GIT_REPO = exports.TXT_COMMAND_PROJECT_LINK_DIR_CONFIRM = exports.TXT_COMMAND_PROJECT_LINK_DIR_LINKED = exports.ERR_COMMAND_PROJECT_LINK_DIR_CREATE = exports.OPT_COMMAND_PROJECT_LINK_TRY_NOW = exports.OPT_COMMAND_PROJECT_LINK_SET_GIT_REMOTE = exports.OPT_COMMAND_PROJECT_LINK_HAS_REMOTE = exports.OPT_COMMAND_PROJECT_LINK_DIRECTORY = exports.OPT_COMMAND_PROJECT_LINK_GIT = exports.OPT_COMMAND_PROJECT_LINK_RELINK = exports.DESC_COMMAND_PROJECT_LINK = exports.DESC_COMMAND_PROJECT_LIST = exports.DESC_COMMAND_PROJECT_GIT = exports.DESC_COMMAND_PROJECT_GIT_CREDENTIAL = exports.DESC_COMMAND_PROJECT = exports.TXT_ARTIFACT_VERSION_DOWNLOAD = exports.TXT_ARTIFACT_PUBLISH = exports.TXT_ARTIFACT_CREATED = exports.TXT_ARTIFACT_VERSION_DELETED = exports.TXT_ARTIFACT_DELETED = exports.ERR_COMMAND_ARTIFACT_TYPE = exports.OPT_COMMAND_ARTIFACT_VERSION = exports.OPT_COMMAND_ARTIFACT_IDENTIFIER = exports.OPT_COMMAND_ARTIFACT_CREATE_IDENTIFIER = exports.OPT_COMMAND_ARTIFACT_NAME = exports.OPT_COMMAND_ARTIFACT_TYPE = exports.DESC_COMMAND_ARTIFACT_CREATE = exports.DESC_COMMAND_ARTIFACT_COMPOSER_PUBLISH = void 0;
23
23
  exports.HELP_PIPELINE_RUN_JSONL = exports.EXAMPLE_PIPELINE_RUN_STATUS = exports.EXAMPLE_PIPELINE_RUN_START = exports.EXAMPLE_SANDBOX_ENDPOINT_LIST = exports.EXAMPLE_SANDBOX_ENDPOINT_GET = exports.EXAMPLE_SANDBOX_ENDPOINT_DELETE = exports.EXAMPLE_SANDBOX_ENDPOINT_CREATE = exports.EXAMPLE_ENVIRONMENT_DELETE = exports.EXAMPLE_ENVIRONMENT_UPDATE = exports.EXAMPLE_ENVIRONMENT_CREATE = exports.EXAMPLE_ENVIRONMENT_GET = exports.EXAMPLE_ENVIRONMENT_LIST = exports.EXAMPLE_TARGET_EXEC_LOGS = exports.EXAMPLE_TARGET_EXEC_LIST = exports.EXAMPLE_TARGET_EXEC_KILL = exports.EXAMPLE_TARGET_EXEC_STATUS = exports.EXAMPLE_TARGET_EXEC_COMMAND = exports.EXAMPLE_TARGET_DELETE = exports.EXAMPLE_TARGET_UPDATE = exports.EXAMPLE_TARGET_CREATE = exports.EXAMPLE_TARGET_GET = exports.EXAMPLE_TARGET_LIST = exports.EXAMPLE_SANDBOX_SNAPSHOT_LIST = exports.EXAMPLE_SANDBOX_SNAPSHOT_GET = exports.EXAMPLE_SANDBOX_SNAPSHOT_DELETE = exports.EXAMPLE_SANDBOX_SNAPSHOT_CREATE = exports.EXAMPLE_SANDBOX_EXEC_STATUS = exports.EXAMPLE_SANDBOX_EXEC_LOGS = exports.EXAMPLE_SANDBOX_APP_STATUS = exports.EXAMPLE_SANDBOX_APP_STOP = exports.EXAMPLE_SANDBOX_APP_REMOVE = exports.EXAMPLE_SANDBOX_APP_ADD = exports.EXAMPLE_SANDBOX_APP_START = exports.EXAMPLE_SANDBOX_APP_LOGS = exports.EXAMPLE_SANDBOX_EXEC_LIST = exports.EXAMPLE_SANDBOX_APP_LIST = exports.EXAMPLE_SANDBOX_EXEC_KILL = exports.EXAMPLE_SANDBOX_SSH = exports.EXAMPLE_SANDBOX_EXEC_COMMAND = exports.EXAMPLE_TUNNEL_START = exports.EXAMPLE_TUNNEL_TCP = exports.EXAMPLE_TUNNEL_TLS = exports.EXAMPLE_AGENT_TUNNEL_START = exports.EXAMPLE_AGENT_TUNNEL_STATUS = exports.EXAMPLE_AGENT_TUNNEL_REMOVE = exports.EXAMPLE_AGENT_TUNNEL_LIST = exports.EXAMPLE_TUNNEL_HTTP = exports.EXAMPLE_SANDBOX_CREATE = exports.EXAMPLE_DISTRO_CREATE = exports.EXAMPLE_ROUTE_CREATE = void 0;
24
24
  exports.OPTION_YAML_VALIDATE_FILE = exports.DESC_COMMAND_YAML_VALIDATE = exports.DESC_COMMAND_YAML_ACTIONS_INFO = exports.DESC_COMMAND_YAML_ACTIONS_LIST = exports.DESC_COMMAND_YAML_ACTIONS = exports.DESC_COMMAND_YAML_PIPELINE = exports.DESC_COMMAND_YAML = exports.TXT_ACTION_SUGGESTED_FROM_REPO = exports.TXT_ACTION_USE_LIST = exports.TXT_ACTION_DID_YOU_MEAN = exports.ERR_ACTION_SEARCH_NO_RESULTS = exports.ERR_ACTION_NOT_FOUND = exports.OPTION_ACTION_TYPE = exports.OPTION_ACTION_SEARCH = exports.EXAMPLE_YAML_VALIDATE = exports.EXAMPLE_YAML_ACTIONS_INFO = exports.EXAMPLE_YAML_ACTIONS_LIST = exports.EXAMPLE_YAML_PIPELINE = exports.EXAMPLE_CRAWL_LINK = exports.EXAMPLE_CRAWL_RUN = exports.EXAMPLE_TESTS_CAPTURE = exports.EXAMPLE_TESTS_VISUAL_SESSION_CLOSE = exports.EXAMPLE_TESTS_VISUAL_SESSION_CREATE = exports.EXAMPLE_TESTS_VISUAL_LINK = exports.EXAMPLE_TESTS_VISUAL_UPLOAD = exports.EXAMPLE_TESTS_VISUAL_SETUP = exports.EXAMPLE_TESTS_UNIT_LINK = exports.EXAMPLE_TESTS_UNIT_UPLOAD = exports.EXAMPLE_ARTIFACT_VERSION_DELETE = exports.EXAMPLE_ARTIFACT_VERSION_GET = exports.EXAMPLE_ARTIFACT_VERSION_LIST = exports.EXAMPLE_ARTIFACT_CREATE = exports.EXAMPLE_ARTIFACT_DELETE = exports.EXAMPLE_ARTIFACT_DOWNLOAD = exports.EXAMPLE_ARTIFACT_PUBLISH = exports.EXAMPLE_ARTIFACT_COMPOSER_PUBLISH = exports.EXAMPLE_ARTIFACT_VERSION_CREATE = exports.EXAMPLE_PIPELINE_RUN_RETRY = exports.EXAMPLE_PIPELINE_RUN_LIST = exports.EXAMPLE_PIPELINE_LIST = exports.EXAMPLE_PIPELINE_YML = exports.EXAMPLE_PIPELINE_GET = exports.EXAMPLE_PIPELINE_UPDATE = exports.EXAMPLE_PIPELINE_DELETE = exports.EXAMPLE_PIPELINE_CREATE = exports.EXAMPLE_PIPELINE_RUN_CANCEL = exports.EXAMPLE_PIPELINE_RUN_APPLY = exports.EXAMPLE_PIPELINE_RUN_LOGS = exports.HELP_PIPELINE_RUN_LOGS = exports.HELP_PIPELINE_RUN_STATUS = void 0;
25
- exports.HELP_AGENT_STATUS_JSON = exports.HELP_SANDBOX_APP_ADD_JSON = exports.EXAMPLE_CONFIG_GET_TUNNELS = exports.EXAMPLE_CONFIG_GET_TUNNEL = exports.HELP_TARGET_EXEC_LOGS_JSONL = exports.HELP_SANDBOX_EXEC_COMMAND_JSONL = exports.HELP_SANDBOX_EXEC_JSONL = exports.HELP_TARGET_EXEC_COMMAND = exports.HELP_TARGET_EXEC_STATUS = exports.HELP_SANDBOX_EXEC_STATUS = exports.HELP_API_INFO_JSON = exports.HELP_DOMAIN_SEARCH_JSON = exports.HELP_YAML_VALIDATE_JSON = exports.EXAMPLE_AGENT_TAGS_GET = exports.EXAMPLE_AGENT_PROXY_STATUS = exports.EXAMPLE_AGENT_TARGET_STATUS = exports.EXAMPLE_AGENT_VERSION = exports.EXAMPLE_AGENT_STATUS = exports.EXAMPLE_DOMAIN_SEARCH = exports.EXAMPLE_API_LIST = exports.EXAMPLE_API_INFO = exports.ERR_API_SCHEMA_NOT_FOUND = exports.EXAMPLE_SANDBOX_LOGS = exports.ERR_YAML_SCHEMA_READ_FAILED = exports.ERR_YAML_SCHEMA_FETCH_FAILED = exports.TXT_YAML_UNKNOWN_TOP_LEVEL_KEYS = exports.ERR_YAML_ITEM_UNKNOWN = exports.ERR_YAML_ITEM_MISSING = exports.ERR_YAML_MUST_BE = exports.ERR_YAML_PIPELINE_NOT_OBJECT = exports.ERR_YAML_EMPTY_ARRAY = exports.ERR_YAML_PLAIN_OBJECT = exports.ERR_YAML_NOT_ARRAY = exports.ERR_YAML_PARSE = exports.ERR_YAML_VALIDATE_FAILED_COUNT = exports.TXT_YAML_VALIDATE_ALL_PASS = exports.ERR_YAML_VALIDATE_FAILED = exports.ERR_YAML_VALIDATE_FILE_NOT_FOUND = void 0;
25
+ exports.OPTION_CONTEXT_TOKEN = exports.OPTION_CONTEXT_REMOTE = exports.OPTION_CONTEXT_GIT = exports.OPTION_CONTEXT_LINK = exports.OPTION_CONTEXT_NAME = exports.DESC_CONTEXT_REMOVE = exports.DESC_CONTEXT_USE = exports.DESC_CONTEXT_SHOW = exports.DESC_CONTEXT_LIST = exports.DESC_CONTEXT_ADD = exports.DESC_CONTEXT = exports.OPTION_CONTEXT = exports.HELP_AGENT_STATUS_JSON = exports.HELP_SANDBOX_APP_ADD_JSON = exports.EXAMPLE_CONFIG_GET_TUNNELS = exports.EXAMPLE_CONFIG_GET_TUNNEL = exports.HELP_TARGET_EXEC_LOGS_JSONL = exports.HELP_SANDBOX_EXEC_COMMAND_JSONL = exports.HELP_SANDBOX_EXEC_JSONL = exports.HELP_TARGET_EXEC_COMMAND = exports.HELP_TARGET_EXEC_STATUS = exports.HELP_SANDBOX_EXEC_STATUS = exports.HELP_API_INFO_JSON = exports.HELP_DOMAIN_SEARCH_JSON = exports.HELP_YAML_VALIDATE_JSON = exports.EXAMPLE_AGENT_TAGS_GET = exports.EXAMPLE_AGENT_PROXY_STATUS = exports.EXAMPLE_AGENT_TARGET_STATUS = exports.EXAMPLE_AGENT_VERSION = exports.EXAMPLE_AGENT_STATUS = exports.EXAMPLE_DOMAIN_SEARCH = exports.EXAMPLE_API_LIST = exports.EXAMPLE_API_INFO = exports.ERR_API_SCHEMA_NOT_FOUND = exports.EXAMPLE_SANDBOX_LOGS = exports.ERR_YAML_SCHEMA_READ_FAILED = exports.ERR_YAML_SCHEMA_FETCH_FAILED = exports.TXT_YAML_UNKNOWN_TOP_LEVEL_KEYS = exports.ERR_YAML_ITEM_UNKNOWN = exports.ERR_YAML_ITEM_MISSING = exports.ERR_YAML_MUST_BE = exports.ERR_YAML_PIPELINE_NOT_OBJECT = exports.ERR_YAML_EMPTY_ARRAY = exports.ERR_YAML_PLAIN_OBJECT = exports.ERR_YAML_NOT_ARRAY = exports.ERR_YAML_PARSE = exports.ERR_YAML_VALIDATE_FAILED_COUNT = exports.TXT_YAML_VALIDATE_ALL_PASS = exports.ERR_YAML_VALIDATE_FAILED = exports.ERR_YAML_VALIDATE_FILE_NOT_FOUND = void 0;
26
+ exports.HELP_CONTEXT_REMOVE = exports.HELP_CONTEXT_USE = exports.HELP_CONTEXT_SHOW = exports.HELP_CONTEXT_LIST = exports.HELP_CONTEXT_ADD = exports.EXAMPLE_CONTEXT_REMOVE = exports.EXAMPLE_CONTEXT_USE = exports.EXAMPLE_CONTEXT_SHOW = exports.EXAMPLE_CONTEXT_LIST = exports.EXAMPLE_CONTEXT_ADD = exports.TXT_CONTEXT_TOKEN_MISSING = exports.TXT_CONTEXT_TOKEN_AVAILABLE = exports.TXT_CONTEXT_TOKEN_LABEL = exports.TXT_CONTEXT_REMOTE_LABEL = exports.TXT_CONTEXT_API_LABEL = exports.TXT_CONTEXT_PROJECT_LABEL = exports.TXT_CONTEXT_WORKSPACE_LABEL = exports.TXT_CONTEXT_SOURCE_LABEL = exports.TXT_CONTEXT_LABEL = exports.TXT_CONTEXT_REMOVE_CONFIRM = exports.ERR_CONTEXT_GIT_FAILED = exports.ERR_CONTEXT_GIT_TIMEOUT = exports.TXT_CONTEXT_PROJECT_HINT = exports.TXT_CONTEXT_WORKSPACE_HINT = exports.HELP_CONTEXT = exports.TXT_CONTEXT_REMOVED = exports.TXT_CONTEXT_ACTIVE = exports.TXT_CONTEXT_SAVED = exports.ERR_CONTEXT_LINK_INVALID = exports.ERR_CONTEXT_LINK_MISSING = exports.ERR_CONTEXT_GIT_URL = exports.ERR_CONTEXT_GIT_REMOTE = exports.ERR_CONTEXT_GIT_REPO = exports.ERR_CONTEXT_GIT_MISSING = exports.ERR_CONTEXT_API = exports.ERR_CONTEXT_PROJECT = exports.ERR_CONTEXT_SOURCE = exports.ERR_CONTEXT_NAME = exports.ERR_CONTEXT_EXISTS = exports.ERR_CONTEXT_RESERVED = exports.ERR_CONTEXT_UNKNOWN = exports.TXT_CONTEXT_LINK_HINT = exports.TXT_CONTEXT_GIT_HINT = exports.TXT_CONTEXT_HINT = exports.OPTION_CONTEXT_API = void 0;
26
27
  const utils_1 = require("./utils");
27
28
  exports.ERR_CANCELLED = 'Cancelled';
28
29
  const TXT_CLI_USAGE_HINT = (command) => `Run '${command} --help' for usage.`;
@@ -59,8 +60,8 @@ const WARN_REST_API_RATE_LIMIT_LOW = (remaining, rateLimitReset) => {
59
60
  };
60
61
  exports.WARN_REST_API_RATE_LIMIT_LOW = WARN_REST_API_RATE_LIMIT_LOW;
61
62
  // Shown on stderr when a list command narrows to PROJECT scope implicitly
62
- // (linked project / BUDDY_PROJECT), so the narrowing is never silent.
63
- const TXT_LIST_PROJECT_SCOPE_NOTE = (resource, project) => `Listing ${resource} of project "${project}" (inferred from the linked project or BUDDY_PROJECT). Use --scope WORKSPACE to list workspace ${resource}.`;
63
+ // (context / linked project / BUDDY_PROJECT), so the narrowing is never silent.
64
+ const TXT_LIST_PROJECT_SCOPE_NOTE = (resource, project) => `Listing ${resource} of project "${project}" (inferred from the context, linked project or BUDDY_PROJECT). Use --scope WORKSPACE to list workspace ${resource}.`;
64
65
  exports.TXT_LIST_PROJECT_SCOPE_NOTE = TXT_LIST_PROJECT_SCOPE_NOTE;
65
66
  exports.ERR_REST_API_URL = 'Valid rest api endpoint is required (--api)';
66
67
  exports.ERR_REST_API_REGION = 'Valid rest api region is required (eu, us, as, onprem)';
@@ -514,6 +515,7 @@ exports.EXAMPLE_DISCOVER = [
514
515
  },
515
516
  ];
516
517
  exports.DISCOVERY_TIPS = [
518
+ "Use --context git in a Buddy clone, --context link after 'bdy project link', or --context default for legacy configuration. Inspect resolved values with 'bdy context show git --format json'. Prefer --context per invocation in scripts; 'context use' changes the global selection.",
517
519
  "Authenticate with 'bdy login'. Discovery itself needs no login or network access.",
518
520
  "Set project context with 'bdy project link', or use --workspace / --project and BUDDY_WORKSPACE / BUDDY_PROJECT where supported.",
519
521
  'Human-readable identifiers and numeric IDs are not interchangeable. Check the argument description.',
@@ -2908,3 +2910,126 @@ JSON OUTPUT:
2908
2910
  An installed but stopped agent returns running=false and exits successfully.
2909
2911
  Version is null when unavailable. A missing or unreadable installation is an error.
2910
2912
  `;
2913
+ exports.OPTION_CONTEXT = 'Connection and scope context: default, git, link or a saved name. Overrides the globally active context for this invocation.';
2914
+ exports.DESC_CONTEXT = 'Manage connection and workspace/project contexts';
2915
+ exports.DESC_CONTEXT_ADD = 'Save a named context without activating it';
2916
+ exports.DESC_CONTEXT_LIST = 'List built-in and saved contexts without resolving their sources';
2917
+ exports.DESC_CONTEXT_SHOW = 'Show resolved context and value sources without changing the active context';
2918
+ exports.DESC_CONTEXT_USE = 'Select the globally active context';
2919
+ exports.DESC_CONTEXT_REMOVE = 'Remove a saved context; removing the active context selects default';
2920
+ exports.OPTION_CONTEXT_NAME = 'Context name';
2921
+ exports.OPTION_CONTEXT_LINK = 'Resolve workspace and project from .bdy/project.json in the current directory';
2922
+ exports.OPTION_CONTEXT_GIT = 'Resolve workspace and project from a Buddy Git remote in the current repository';
2923
+ exports.OPTION_CONTEXT_REMOTE = 'Git remote to read with --from-git. Default: origin';
2924
+ exports.OPTION_CONTEXT_TOKEN = 'REST API token for this context; omitted means inherit the default authentication mechanism';
2925
+ exports.OPTION_CONTEXT_API = 'HTTPS API endpoint for this context; omitted means inherit the default endpoint mechanism';
2926
+ exports.TXT_CONTEXT_HINT = "Run 'bdy context list' to see available contexts, or use --context default.";
2927
+ exports.TXT_CONTEXT_GIT_HINT = "Run in a Buddy clone with an origin remote, or create a context with 'bdy context add <name> --from-git --remote <remote>'.";
2928
+ exports.TXT_CONTEXT_LINK_HINT = "Run 'bdy project link' in this directory, or use --context default.";
2929
+ const ERR_CONTEXT_UNKNOWN = (name) => `Context not found: ${name}`;
2930
+ exports.ERR_CONTEXT_UNKNOWN = ERR_CONTEXT_UNKNOWN;
2931
+ exports.ERR_CONTEXT_RESERVED = 'The built-in contexts default, git and link cannot be changed or removed.';
2932
+ const ERR_CONTEXT_EXISTS = (name) => `Context already exists: ${name}. Remove it before creating a replacement.`;
2933
+ exports.ERR_CONTEXT_EXISTS = ERR_CONTEXT_EXISTS;
2934
+ exports.ERR_CONTEXT_NAME = 'Use a context name containing letters, numbers, underscores or hyphens, starting with a letter or number.';
2935
+ exports.ERR_CONTEXT_SOURCE = 'Choose exactly one scope source: workspace/project values, --from-link or --from-git. --remote requires --from-git.';
2936
+ exports.ERR_CONTEXT_PROJECT = 'A context with a project must also specify a workspace.';
2937
+ exports.ERR_CONTEXT_API = 'Provide an HTTPS API endpoint without credentials, a path, query or fragment.';
2938
+ exports.ERR_CONTEXT_GIT_MISSING = 'Git is not installed or cannot be executed.';
2939
+ exports.ERR_CONTEXT_GIT_REPO = 'The current directory is not an accessible Git repository.';
2940
+ const ERR_CONTEXT_GIT_REMOTE = (remote) => `Git remote not found or unreadable: ${remote}`;
2941
+ exports.ERR_CONTEXT_GIT_REMOTE = ERR_CONTEXT_GIT_REMOTE;
2942
+ exports.ERR_CONTEXT_GIT_URL = 'The selected remote is not a supported Buddy repository URL.';
2943
+ exports.ERR_CONTEXT_LINK_MISSING = 'No project link exists in the current directory.';
2944
+ exports.ERR_CONTEXT_LINK_INVALID = 'The project link is unreadable or does not contain a workspace and project.';
2945
+ const TXT_CONTEXT_SAVED = (name) => `Context saved: ${name}`;
2946
+ exports.TXT_CONTEXT_SAVED = TXT_CONTEXT_SAVED;
2947
+ const TXT_CONTEXT_ACTIVE = (name) => `Active context: ${name}`;
2948
+ exports.TXT_CONTEXT_ACTIVE = TXT_CONTEXT_ACTIVE;
2949
+ const TXT_CONTEXT_REMOVED = (name) => `Context removed: ${name}`;
2950
+ exports.TXT_CONTEXT_REMOVED = TXT_CONTEXT_REMOVED;
2951
+ exports.HELP_CONTEXT = `
2952
+ Built-in contexts:
2953
+ default: existing flags, environment and configuration rules, including sandbox precedence.
2954
+ git: workspace/project from remote origin of the current Buddy repository (HTTPS or SSH).
2955
+ link: workspace/project from .bdy/project.json in the current directory; no ancestor search.
2956
+
2957
+ Named contexts inherit authentication and endpoint resolution when omitted.
2958
+ Workspace and project never inherit from default, environment or project links.
2959
+ --from-link and --from-git save a dynamic source, not its current values.
2960
+ An unavailable source is an error; sources never fall back to each other.
2961
+ Explicit command flags override context values. Changing workspace drops the
2962
+ context project unless --project is also given. --scope workspace keeps workspace scope.
2963
+ Contexts do not select pipeline branches or revisions.
2964
+
2965
+ --context <name> overrides the active context for one invocation.
2966
+ Without it, the saved active context is used; initially this is default.
2967
+ Use per-invocation selection in concurrent scripts and agents.
2968
+ show resolves locally without authenticating or making API calls. Token output
2969
+ contains only availability and source, never its value; availability does not
2970
+ prove authentication. list does not resolve Git or project links.
2971
+ Built-in names are reserved. Removing an active custom context selects default.
2972
+ login/logout, register, workspace set and project link use the default configuration.
2973
+ `;
2974
+ exports.TXT_CONTEXT_WORKSPACE_HINT = 'Pass --workspace <domain> or select a context containing a workspace.';
2975
+ exports.TXT_CONTEXT_PROJECT_HINT = 'Pass --project <name> or select a context containing a project.';
2976
+ exports.ERR_CONTEXT_GIT_TIMEOUT = 'Git timed out while reading the repository remote.';
2977
+ exports.ERR_CONTEXT_GIT_FAILED = 'Git could not read the repository remote.';
2978
+ const TXT_CONTEXT_REMOVE_CONFIRM = (name) => `Remove context "${name}" and its saved token? If active, default will be selected.`;
2979
+ exports.TXT_CONTEXT_REMOVE_CONFIRM = TXT_CONTEXT_REMOVE_CONFIRM;
2980
+ exports.TXT_CONTEXT_LABEL = 'Context';
2981
+ exports.TXT_CONTEXT_SOURCE_LABEL = 'Source';
2982
+ exports.TXT_CONTEXT_WORKSPACE_LABEL = 'Workspace';
2983
+ exports.TXT_CONTEXT_PROJECT_LABEL = 'Project';
2984
+ exports.TXT_CONTEXT_API_LABEL = 'API';
2985
+ exports.TXT_CONTEXT_REMOTE_LABEL = 'Remote';
2986
+ exports.TXT_CONTEXT_TOKEN_LABEL = 'Token';
2987
+ exports.TXT_CONTEXT_TOKEN_AVAILABLE = 'available';
2988
+ exports.TXT_CONTEXT_TOKEN_MISSING = 'missing';
2989
+ exports.EXAMPLE_CONTEXT_ADD = [
2990
+ { description: 'Save a fixed workspace and project', command: 'bdy context add backend --workspace acme --project backend' },
2991
+ { description: 'Resolve the buddy remote on each invocation', command: 'bdy context add company --from-git --remote buddy' },
2992
+ ];
2993
+ exports.EXAMPLE_CONTEXT_LIST = [
2994
+ { description: 'List contexts and the active selection as JSON', command: 'bdy context list --format json' },
2995
+ ];
2996
+ exports.EXAMPLE_CONTEXT_SHOW = [
2997
+ { description: 'Inspect the current repository scope without switching contexts', command: 'bdy context show git --format json' },
2998
+ ];
2999
+ exports.EXAMPLE_CONTEXT_USE = [
3000
+ { description: 'Restore the default configuration for subsequent invocations', command: 'bdy context use default' },
3001
+ ];
3002
+ exports.EXAMPLE_CONTEXT_REMOVE = [
3003
+ { description: 'Remove a saved context without prompting', command: 'bdy context remove backend --force' },
3004
+ ];
3005
+ exports.HELP_CONTEXT_ADD = `
3006
+ Save exactly one scope source: explicit --workspace/--project, --from-link or --from-git.
3007
+ --remote requires --from-git and defaults to origin. Dynamic sources are resolved
3008
+ on each invocation, not when saved. Omitted token and endpoint inherit default
3009
+ resolution; workspace and project do not inherit. Adding does not activate the context.
3010
+ Built-in names are reserved; an existing name cannot be overwritten.
3011
+ `;
3012
+ exports.HELP_CONTEXT_LIST = `
3013
+ Lists built-in and saved contexts without reading Git or project links.
3014
+ Text shows names, with * marking the globally active context. JSON includes
3015
+ name, active, builtin and source. No credentials are printed.
3016
+ `;
3017
+ exports.HELP_CONTEXT_SHOW = `
3018
+ Resolves the selected context locally without authenticating or making API calls.
3019
+ An optional name overrides the invocation's --context or globally active context
3020
+ for this inspection only. JSON includes workspace, project, api, remote and
3021
+ sources. Token output contains only availability and source, never its value.
3022
+ Availability does not prove authentication. Dynamic sources resolve in the current directory.
3023
+ `;
3024
+ exports.HELP_CONTEXT_USE = `
3025
+ Saves the active context for subsequent invocations without resolving its scope.
3026
+ For scripts and parallel agents, prefer --context <name> on each command.
3027
+ Use default to restore the existing configuration rules.
3028
+ `;
3029
+ exports.HELP_CONTEXT_REMOVE = `
3030
+ Removes a saved context and its token after confirmation; --force skips the prompt.
3031
+ Built-in contexts cannot be removed. Removing the active context selects default.
3032
+ A declined confirmation leaves both the context and selection unchanged. In JSON,
3033
+ removed is the deleted name, or null when declined; active is the current selection.
3034
+ Non-interactive commands decline without --force.
3035
+ `;
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  const path_1 = require("path");
4
7
  const fs_1 = require("fs");
5
8
  const utils_1 = require("../utils");
9
+ const cliError_1 = __importDefault(require("../cliError"));
6
10
  const texts_1 = require("../texts");
11
+ const texts_2 = require("../texts");
7
12
  const tunnel_1 = require("../types/tunnel");
8
13
  class Cfg {
9
14
  dir;
@@ -22,7 +27,7 @@ class Cfg {
22
27
  });
23
28
  }
24
29
  catch {
25
- throw new Error((0, texts_1.ERR_CANT_CREATE_DIR_IN_HOME)('.bdy'));
30
+ throw new Error((0, texts_2.ERR_CANT_CREATE_DIR_IN_HOME)('.bdy'));
26
31
  }
27
32
  try {
28
33
  (0, fs_1.chmodSync)(this.dir, 0o700);
@@ -36,7 +41,7 @@ class Cfg {
36
41
  (0, fs_1.writeFileSync)(this.file, '{}', 'utf-8');
37
42
  }
38
43
  catch {
39
- throw new Error((0, texts_1.ERR_CANT_CREATE_DIR_IN_HOME)('.bdy/cfg.json'));
44
+ throw new Error((0, texts_2.ERR_CANT_CREATE_DIR_IN_HOME)('.bdy/cfg.json'));
40
45
  }
41
46
  try {
42
47
  (0, fs_1.chmodSync)(this.file, 0o600);
@@ -252,6 +257,40 @@ class Cfg {
252
257
  getBaseUrl() {
253
258
  return this.json.baseUrl || '';
254
259
  }
260
+ getContexts() {
261
+ return { ...this.json.contexts };
262
+ }
263
+ getActiveContext() {
264
+ return this.json.activeContext || 'default';
265
+ }
266
+ getContext(name) {
267
+ if (name === 'default')
268
+ return null;
269
+ if (name === 'git')
270
+ return { source: 'git', remote: 'origin' };
271
+ if (name === 'link')
272
+ return { source: 'link' };
273
+ const contexts = this.getContexts();
274
+ if (!Object.hasOwn(contexts, name))
275
+ throw new cliError_1.default('context_not_found', (0, texts_1.ERR_CONTEXT_UNKNOWN)(name), texts_1.TXT_CONTEXT_HINT);
276
+ return contexts[name];
277
+ }
278
+ setContext(name, context) {
279
+ this.json.contexts = { ...this.getContexts(), [name]: context };
280
+ this.save();
281
+ }
282
+ setActiveContext(name) {
283
+ this.getContext(name);
284
+ this.json.activeContext = name;
285
+ this.save();
286
+ }
287
+ removeContext(name) {
288
+ this.getContext(name);
289
+ delete this.json.contexts[name];
290
+ if (this.getActiveContext() === name)
291
+ this.json.activeContext = 'default';
292
+ this.save();
293
+ }
255
294
  clearLogin() {
256
295
  delete this.json.apiToken;
257
296
  delete this.json.apiRefreshToken;
@@ -282,7 +321,7 @@ class Cfg {
282
321
  this.json = JSON.parse(txt);
283
322
  }
284
323
  catch {
285
- throw new Error(texts_1.ERR_CONFIG_CORRUPTED);
324
+ throw new Error(texts_2.ERR_CONFIG_CORRUPTED);
286
325
  }
287
326
  }
288
327
  save() {
@@ -291,7 +330,7 @@ class Cfg {
291
330
  (0, fs_1.writeFileSync)(this.file, JSON.stringify(this.json, null, 4), 'utf-8');
292
331
  }
293
332
  catch {
294
- throw new Error((0, texts_1.ERR_CANT_CREATE_DIR_IN_HOME)('.bdy/cfg.json'));
333
+ throw new Error((0, texts_2.ERR_CANT_CREATE_DIR_IN_HOME)('.bdy/cfg.json'));
295
334
  }
296
335
  try {
297
336
  (0, fs_1.chmodSync)(this.file, 0o600);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.25.6-dev",
4
+ "version": "1.25.7-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",