bdy 1.23.16-dev-target-commands → 1.23.16-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.
Files changed (42) hide show
  1. package/distTs/README.md +181 -0
  2. package/distTs/package.json +3 -3
  3. package/distTs/src/agent/agent.js +27 -2
  4. package/distTs/src/agent/linux.js +7 -5
  5. package/distTs/src/agent/manager.js +25 -0
  6. package/distTs/src/agent/osx.js +7 -9
  7. package/distTs/src/agent/socket/client.js +10 -0
  8. package/distTs/src/agent/system.js +16 -4
  9. package/distTs/src/agent/windows.js +1 -2
  10. package/distTs/src/api/client.js +5 -2
  11. package/distTs/src/command/agent/install.js +1 -1
  12. package/distTs/src/command/agent/run.js +16 -0
  13. package/distTs/src/command/project/get.js +18 -0
  14. package/distTs/src/command/project/set.js +31 -0
  15. package/distTs/src/command/sandbox/app/list.js +6 -1
  16. package/distTs/src/command/sandbox/create.js +2 -2
  17. package/distTs/src/command/sandbox/get/yaml.js +30 -0
  18. package/distTs/src/command/vt/scrape.js +193 -0
  19. package/distTs/src/input.js +16 -1
  20. package/distTs/src/logger.js +7 -1
  21. package/distTs/src/texts.js +28 -22
  22. package/distTs/src/tunnel/server/sftp.js +24 -5
  23. package/distTs/src/tunnel/ssh/client.js +35 -4
  24. package/distTs/src/types/tunnel.js +11 -0
  25. package/distTs/src/utils.js +2 -2
  26. package/package.json +3 -3
  27. package/distTs/detect-rules.json +0 -351
  28. package/distTs/src/command/pipeline/run/apply.js +0 -62
  29. package/distTs/src/command/yaml/actions/detect.js +0 -268
  30. package/distTs/src/command/yaml/actions/info.js +0 -56
  31. package/distTs/src/command/yaml/actions/list.js +0 -70
  32. package/distTs/src/command/yaml/actions/schema.js +0 -104
  33. package/distTs/src/command/yaml/actions.js +0 -13
  34. package/distTs/src/command/yaml/agents.js +0 -88
  35. package/distTs/src/command/yaml/cache.js +0 -98
  36. package/distTs/src/command/yaml/init.js +0 -110
  37. package/distTs/src/command/yaml/pipeline.js +0 -42
  38. package/distTs/src/command/yaml/render.js +0 -83
  39. package/distTs/src/command/yaml/schemaUtils.js +0 -139
  40. package/distTs/src/command/yaml/validate.js +0 -259
  41. package/distTs/src/command/yaml.js +0 -16
  42. package/distTs/src/diskCache.js +0 -82
@@ -0,0 +1,181 @@
1
+ # Buddy CLI (`bdy`)
2
+
3
+ Work seamlessly with [Buddy](https://buddy.works) from the command line.
4
+
5
+ `bdy` is Buddy's official command-line interface for managing CI/CD pipelines, agents, tests, artifacts, sandboxes, distributions, and more — straight from your terminal.
6
+
7
+ 📚 Full documentation: https://buddy.works/docs/cli/getting-started
8
+
9
+ ## Installation
10
+
11
+ ### npm (recommended)
12
+
13
+ Requires Node.js 14+.
14
+
15
+ ```bash
16
+ # macOS / Linux
17
+ sudo npm i -g bdy
18
+
19
+ # Windows
20
+ npm i -g bdy
21
+ ```
22
+
23
+ ### Homebrew (macOS)
24
+
25
+ ```bash
26
+ brew tap buddy/bdy
27
+ brew install bdy
28
+ ```
29
+
30
+ ### APT (Linux x64 / ARM64)
31
+
32
+ ```bash
33
+ sudo apt-get update && sudo apt-get install -y software-properties-common
34
+ sudo gpg --homedir /tmp --no-default-keyring --keyring /usr/share/keyrings/buddy.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys eb39332e766364ca6220e8dc631c5a16310cc0ad
35
+ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/buddy.gpg] https://es.buddy.works/bdy/apt-repo prod main" | sudo tee /etc/apt/sources.list.d/buddy.list > /dev/null
36
+ sudo apt-get update && sudo apt-get install -y bdy
37
+ ```
38
+
39
+ > On ARM64, replace `arch=amd64` with `arch=arm64` in the `echo` command above.
40
+
41
+ ### Chocolatey (Windows)
42
+
43
+ ```powershell
44
+ choco install bdy --pre
45
+ ```
46
+
47
+ ### Direct download
48
+
49
+ Prebuilt binaries are available for macOS (Apple Silicon), Linux (x64/ARM64), and Windows (x64) on the [installation page](https://buddy.works/docs/cli/getting-started).
50
+
51
+ ### Verify & update
52
+
53
+ ```bash
54
+ bdy version # show current and latest version
55
+ sudo npm i -g bdy # update (or use your package manager's upgrade command)
56
+ ```
57
+
58
+ ## Authentication
59
+
60
+ A [Buddy account](https://buddy.works) is required to use the CLI.
61
+
62
+ Log in interactively:
63
+
64
+ ```bash
65
+ bdy login
66
+ ```
67
+
68
+ New to Buddy? Create an account from the CLI:
69
+
70
+ ```bash
71
+ bdy register
72
+ ```
73
+
74
+ ### Linking a project
75
+
76
+ Once logged in, link a directory to a Buddy project:
77
+
78
+ ```bash
79
+ mkdir my-proj
80
+ cd my-proj
81
+ bdy proj link
82
+ ```
83
+
84
+ After linking, every `bdy` command run from this directory is executed against the linked project — no need to pass the project name each time.
85
+
86
+ Check who you're logged in as, or log out:
87
+
88
+ ```bash
89
+ bdy whoami
90
+ bdy logout
91
+ ```
92
+
93
+ ### Tokens & non-interactive login
94
+
95
+ For CI/CD pipelines and non-browser environments, authenticate with a [personal access token](https://buddy.works/docs/api/getting-started/oauth2/personal-access-token):
96
+
97
+ ```bash
98
+ bdy login --token <token>
99
+ ```
100
+
101
+ The login command accepts the following options:
102
+
103
+ | Option | Description | Environment variable |
104
+ | --- | --- | --- |
105
+ | `--token <token>` | Personal access token | `BUDDY_TOKEN` |
106
+ | `--api <url>` | API URL for On-Premises installations | `BUDDY_API_ENDPOINT` |
107
+ | `--region <region>` | Region: `us`, `eu`, or `as` | `BUDDY_REGION` |
108
+ | `-w, --workspace <domain>` | Workspace URL handle | `BUDDY_WORKSPACE` |
109
+
110
+ Each option can be supplied as a flag or via its environment variable — handy for pipelines and headless setups:
111
+
112
+ ```bash
113
+ export BUDDY_TOKEN=<token>
114
+ export BUDDY_WORKSPACE=<your-workspace>
115
+ bdy login
116
+ ```
117
+
118
+ ## Agent integration (plugin & skills)
119
+
120
+ The [Buddy Plugin](https://github.com/buddy/buddy-plugin) lets coding agents deploy applications, expose local services, and manage infrastructure on Buddy. It bundles a consolidated skill covering sandboxes, artifacts, tunnels, domains, distributions, and pipelines, plus two commands:
121
+
122
+ - `/deploy [name] [path]` — deploy a static site or server application
123
+ - `/expose [port]` — open a tunnel to a locally running service
124
+
125
+ Make sure the CLI is installed first (`sudo npm install -g bdy`), then:
126
+
127
+ **Claude Code** — install the plugin (commands + skills):
128
+
129
+ ```bash
130
+ claude plugin marketplace add buddy/buddy-plugin
131
+ claude plugin install buddy@buddy-plugin
132
+ ```
133
+
134
+ **Other agents** — install the skills only:
135
+
136
+ ```bash
137
+ npx skills add buddy/buddy-plugin
138
+ ```
139
+
140
+ Then authenticate and link your project:
141
+
142
+ ```bash
143
+ bdy login
144
+ cd your-project
145
+ bdy proj link
146
+ ```
147
+
148
+ The plugin auto-detects your project type: static sites generate versioned artifacts with public URLs, while server applications deploy to sandboxes with HTTPS endpoints. Monorepos can deploy multiple applications, each getting its own URL and endpoint.
149
+
150
+ ## Commands
151
+
152
+ | Command | Description |
153
+ | --- | --- |
154
+ | `bdy login` / `sign-in` | Log in to Buddy |
155
+ | `bdy register` / `sign-up` | Register a new Buddy account |
156
+ | `bdy whoami` | Check login information |
157
+ | `bdy logout` | Log out from Buddy |
158
+ | `bdy workspace` / `ws` | Manage workspaces |
159
+ | `bdy project` / `proj` | Manage projects |
160
+ | `bdy pipeline` / `pip` | Interact with the pipeline service |
161
+ | `bdy artifact` / `art` | Interact with the artifact service |
162
+ | `bdy sandbox` / `sb` | Interact with sandboxes |
163
+ | `bdy agent` | Install and run `bdy` as an OS service (Windows, macOS, Linux) |
164
+ | `bdy tunnel` | Manage tunnel configuration |
165
+ | `bdy crawl` | Manage web crawls (Markdown, HTML, PNG/JPEG screenshots; suite linking) |
166
+ | `bdy tests` | Manage unit tests and visual regression (Storybook, URL captures, runners) |
167
+ | `bdy domain` | Manage zones used in distribution routes |
168
+ | `bdy distro` | Manage distributions |
169
+ | `bdy api` | Contact the Buddy API directly |
170
+ | `bdy update` / `version` | Show version and update the CLI |
171
+
172
+ Run any command with `-h` / `--help` for detailed usage:
173
+
174
+ ```bash
175
+ bdy --help
176
+ bdy pipeline --help
177
+ ```
178
+
179
+ ## Feedback
180
+
181
+ Found a bug or have a feature request? Let us know through [Buddy support](https://buddy.works/support) or the [documentation](https://buddy.works/docs/cli/getting-started).
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bdy",
3
3
  "preferGlobal": false,
4
- "version": "1.23.16-dev-target-commands",
4
+ "version": "1.23.16-dev",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
7
7
  "homepage": "https://buddy.works/docs/cli",
@@ -53,7 +53,7 @@
53
53
  "@fastify/cors": "11.2.0",
54
54
  "@inquirer/prompts": "8.5.2",
55
55
  "@modelcontextprotocol/sdk": "1.30.0",
56
- "@puppeteer/browsers": "2.13.0",
56
+ "@puppeteer/browsers": "3.2.0",
57
57
  "@scalar/json-magic": "0.12.16",
58
58
  "@scalar/openapi-parser": "0.28.7",
59
59
  "@scalar/openapi-types": "0.9.1",
@@ -81,7 +81,7 @@
81
81
  "picomatch": "4.0.4",
82
82
  "pino": "10.3.1",
83
83
  "punycode": "2.3.1",
84
- "puppeteer-core": "23.11.1",
84
+ "puppeteer-core": "25.6.0",
85
85
  "range-parser": "1.2.1",
86
86
  "socket.io-client": "4.8.3",
87
87
  "ssh2": "1.17.0",
@@ -343,12 +343,16 @@ class Agent extends events_1.default {
343
343
  this.socket.fetch(!this.disabled, null, null, false, this.getTunnelsUpdate(), true);
344
344
  }
345
345
  }
346
- // Matches on the two request values exactly, which is what lets an update status share
347
- // the field: TUNNEL_AGENT_UPDATE_STATUS values fall through here and do nothing, so an
346
+ // Matches on the request values exactly, which is what lets an update status share the
347
+ // field: TUNNEL_AGENT_UPDATE_STATUS values fall through here and do nothing, so an
348
348
  // outcome we reported ourselves is never read back as a fresh request. Anything unknown
349
349
  // is left alone rather than acked, so a value a newer front understands is still there
350
350
  // when the agent is upgraded.
351
351
  async makeAction(action) {
352
+ if (action === tunnel_1.TUNNEL_AGENT_ACTION.DEBUG_ON ||
353
+ action === tunnel_1.TUNNEL_AGENT_ACTION.DEBUG_OFF) {
354
+ return this.makeDebugAction(action === tunnel_1.TUNNEL_AGENT_ACTION.DEBUG_ON);
355
+ }
352
356
  if (action === tunnel_1.TUNNEL_AGENT_ACTION.DELETE) {
353
357
  if (this.manager) {
354
358
  return this.manager.disableAgentAndExit(texts_1.ERR_AGENT_REMOVED);
@@ -373,6 +377,27 @@ class Agent extends events_1.default {
373
377
  return;
374
378
  }
375
379
  }
380
+ // Applied from here rather than through the local api that `agent debug` calls: that api
381
+ // exists so another process can reach a running agent, and this is already inside one.
382
+ // A foreground tunnel has no manager and no config to record the level in, so it only
383
+ // changes its own - which is all it has to change.
384
+ //
385
+ // Always acked, and idempotent on purpose. The backend rewrites the whole agent row per
386
+ // request, so an ack can be undone by a concurrent update and the request comes back on
387
+ // the next fetch (see reclearLostUpdateStatus) - re-applying a level the process already
388
+ // runs at costs nothing, while rewriting the config every 15 seconds would, which is why
389
+ // the state check guards the write and not the ack.
390
+ makeDebugAction(debug) {
391
+ if (logger_1.default.debugOn !== debug) {
392
+ logger_1.default.info((0, texts_1.LOG_AGENT_DEBUG_REMOTE)(debug));
393
+ if (!this.manager)
394
+ logger_1.default.setDebug(debug);
395
+ else if (!this.manager.setDebug(debug)) {
396
+ logger_1.default.info(texts_1.LOG_AGENT_DEBUG_NOT_PERSISTED);
397
+ }
398
+ }
399
+ return this.ackAction();
400
+ }
376
401
  refreshTunnels(tunnels) {
377
402
  if (this.tunnelsUpdating)
378
403
  return;
@@ -49,14 +49,13 @@ class AgentLinux extends system_1.default {
49
49
  // `systemctl show bdy` would hand the token to the same unprivileged users - and file modes
50
50
  // cannot stop that. Closing this means the agent reading the token from a file only it can
51
51
  // read, which is a change of where the token lives, not of this template.
52
- getServiceConfig(id, host, token, port, user, debug) {
52
+ getServiceConfig(id, host, token, port, user) {
53
53
  const cli = this.getSystemBinaryPath();
54
54
  return `[Unit]
55
55
  Description=Buddy Tunnel Agent
56
56
  After=network.target
57
57
 
58
58
  [Service]
59
- Environment="DEBUG=${debug ? 1 : 0}"
60
59
  User=${user || 'root'}
61
60
  WorkingDirectory=/
62
61
  ExecStart=${cli} agent run --id="${id}" --host="${host}" --token="${token}" --port=${port}
@@ -84,7 +83,10 @@ WantedBy=multi-user.target`;
84
83
  const user = m[1].trim();
85
84
  return user === 'root' ? '' : user;
86
85
  }
87
- async install(id, host, token, port, start, user, pass, debug) {
86
+ async install(id, host, token, port, start, user,
87
+ // Windows only - a unit runs as a user without being handed their password
88
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
89
+ _pass) {
88
90
  try {
89
91
  // ensure service clear
90
92
  await this.uninstall();
@@ -105,7 +107,7 @@ WantedBy=multi-user.target`;
105
107
  await this.extractBinaryArchive();
106
108
  await this.fixBinaryRights();
107
109
  logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_SERVICE_CONFIG);
108
- await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user, debug));
110
+ await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user));
109
111
  // Everything above was written by this root process, so the service user does not own
110
112
  // any of it yet and could not write its host key or its log. Ownership first, mode
111
113
  // second - see chownSystemDir for why that order is the one that fails safely.
@@ -135,7 +137,7 @@ WantedBy=multi-user.target`;
135
137
  catch {
136
138
  // do nothing
137
139
  }
138
- await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user, json.debug));
140
+ await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user));
139
141
  // The directory follows the service to its new user, and back to root when the user is
140
142
  // cleared - this is what makes changing the user later work without 0777 everywhere
141
143
  await this.chownSystemDir(user);
@@ -502,6 +502,28 @@ class AgentManagerClass {
502
502
  return;
503
503
  }
504
504
  }
505
+ // The live level, and the record of it for the next start. Both entry points into an
506
+ // agent process read that record back - `agent run` for a service, startStandaloneProc
507
+ // for an `--app` install - so this write is what makes a toggle survive a restart.
508
+ //
509
+ // Best effort on the write, and the level is changed either way. The config normally
510
+ // belongs to the service user (chownSystemDir hands it over at 0644), so this succeeds
511
+ // for a service running as someone other than root - but a directory left behind by an
512
+ // older install, or a read-only filesystem, still ends up here, and refusing to raise
513
+ // the level over a file that only decides the *next* start would take remote debugging
514
+ // away from the machine in front of us. saveSystemConfig logs what went wrong, and
515
+ // makeDebugAction says what it cost.
516
+ setDebug(debug) {
517
+ logger_1.default.setDebug(debug);
518
+ let json;
519
+ try {
520
+ json = this.system.loadSystemConfig();
521
+ }
522
+ catch {
523
+ return false;
524
+ }
525
+ return this.system.saveSystemConfig(json.id, json.host, json.token, json.port, !!json.standalone, debug);
526
+ }
505
527
  async processAgentDebug(req, res) {
506
528
  if (!this.agent) {
507
529
  this.serverError(res, 'Agent not enabled');
@@ -515,6 +537,9 @@ class AgentManagerClass {
515
537
  this.serverError(res, 'Wrong input data');
516
538
  return;
517
539
  }
540
+ // The level only. The `agent debug` command on the other end of this writes the config
541
+ // itself, from a process that has checked it has the rights to - a second write of the
542
+ // same values from here would say nothing the first one does not.
518
543
  logger_1.default.setDebug(!!data.debug);
519
544
  logger_1.default.info(`MANAGER REQUEST: change debug to ${!!data.debug}`);
520
545
  this.serverOutput(res, {
@@ -41,7 +41,7 @@ class AgentOsx extends system_1.default {
41
41
  // this plist, and the same move on systemd leaks the token through `systemctl show` whatever
42
42
  // the file mode is - see the note in AgentLinux.getServiceConfig. Closing this means the agent
43
43
  // reading the token from a file only it can read, which is a change of where the token lives.
44
- getServiceConfig(id, host, token, port, user, debug) {
44
+ getServiceConfig(id, host, token, port, user) {
45
45
  const cli = this.getSystemBinaryPath();
46
46
  let cfg = `<?xml version='1.0' encoding='UTF-8'?>
47
47
  <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -63,11 +63,6 @@ class AgentOsx extends system_1.default {
63
63
  <string>--port</string>
64
64
  <string>${port}</string>
65
65
  </array>
66
- <key>EnvironmentVariables</key>
67
- <dict>
68
- <key>DEBUG</key>
69
- <string>${debug ? 1 : 0}</string>
70
- </dict>
71
66
  <key>KeepAlive</key>
72
67
  <true/>
73
68
  <key>RunAtLoad</key>
@@ -99,7 +94,10 @@ class AgentOsx extends system_1.default {
99
94
  const m = cfg.match(/<key>UserName<\/key>\s*<string>([^<]+)<\/string>/);
100
95
  return m ? m[1].trim() : '';
101
96
  }
102
- async install(id, host, token, port, start, user, pass, debug) {
97
+ async install(id, host, token, port, start, user,
98
+ // Windows only - a launchd job runs as a user without being handed their password
99
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
100
+ _pass) {
103
101
  try {
104
102
  // ensure service clear
105
103
  await this.uninstall();
@@ -120,7 +118,7 @@ class AgentOsx extends system_1.default {
120
118
  await this.extractBinaryArchive();
121
119
  await this.fixBinaryRights();
122
120
  logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_SERVICE_CONFIG);
123
- await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user, debug));
121
+ await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user));
124
122
  // Everything above was written by this root process, so the service user does not own
125
123
  // any of it yet and could not write its host key or its log. Ownership first, mode
126
124
  // second - see chownSystemDir for why that order is the one that fails safely.
@@ -150,7 +148,7 @@ class AgentOsx extends system_1.default {
150
148
  catch {
151
149
  // do nothing
152
150
  }
153
- await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user, json.debug));
151
+ await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user));
154
152
  // The directory follows the service to its new user, and back to root when the user is
155
153
  // cleared - this is what makes changing the user later work without 0777 everywhere
156
154
  await this.chownSystemDir(user);
@@ -55,6 +55,15 @@ class AgentSocketClient extends events_1.default {
55
55
  version: this.version,
56
56
  activate,
57
57
  action: pending !== null ? pending : action,
58
+ // The live level, not the one the config records: DEBUG_ON is answered by this
59
+ // process, so what it runs at is the only answer worth reporting. Nothing reads it
60
+ // yet - the backend takes the fetch apart field by field and has nowhere to put
61
+ // this one, so the front offers both directions instead of a toggle. Sent anyway,
62
+ // and on every emit rather than only after a toggle: the day there is a column for
63
+ // it, every agent already in the field fills it in without being upgraded, and the
64
+ // whole row is rewritten per request so a payload that left it out would be a
65
+ // refresh tick clearing what a toggle had just reported.
66
+ debug: logger_1.default.debugOn,
58
67
  disabled,
59
68
  resetFirstHeard,
60
69
  tunnels,
@@ -81,6 +90,7 @@ class AgentSocketClient extends events_1.default {
81
90
  user: this.user,
82
91
  version: this.version,
83
92
  activate,
93
+ debug: logger_1.default.debugOn,
84
94
  tunnels,
85
95
  });
86
96
  }
@@ -18,7 +18,7 @@ class AgentSystem {
18
18
  serviceMode = false;
19
19
  // extend
20
20
  /* eslint-disable @typescript-eslint/no-unused-vars */
21
- async install(_id, _host, _token, _port, _start, _user, _pass, _debug) {
21
+ async install(_id, _host, _token, _port, _start, _user, _pass) {
22
22
  return Promise.reject();
23
23
  }
24
24
  /* eslint-enable @typescript-eslint/no-unused-vars */
@@ -858,9 +858,21 @@ class AgentSystem {
858
858
  catch {
859
859
  output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
860
860
  }
861
- if (json.debug) {
862
- logger_1.default.setDebug(true);
863
- }
861
+ // Both ways, not only on: the config is what `agent debug` and the remote toggle write,
862
+ // so an agent turned back down starts back down - see the same read in `agent run` for
863
+ // why reading it only when true leaves an install that can never be quietened.
864
+ //
865
+ // The environment still wins upwards, unlike over there. This process is started by
866
+ // hand, so a DEBUG in it was typed by whoever is watching the run - where the one a
867
+ // service inherits is a leftover from the definition an older cli installed. Same OR
868
+ // the level had before this was a config at all: Logger seeds itself from DEBUG, so
869
+ // this only adds the config's ability to state `false`.
870
+ //
871
+ // The one place that assumption does not hold is a container started with `-e DEBUG=1`,
872
+ // where nothing was typed per run: there the remote toggle takes effect immediately but
873
+ // does not survive the container restarting, because the env is back and outranks the
874
+ // config again. Left that way on purpose - an image that asks for debug logs gets them.
875
+ logger_1.default.setDebug(!!json.debug || process.env.DEBUG === '1');
864
876
  const saved = this.saveStandaloneProcConfig();
865
877
  if (!saved) {
866
878
  output_1.default.exitError(texts_1.ERR_SWW_AGENT_ENABLING);
@@ -116,7 +116,7 @@ class AgentWindows extends system_1.default {
116
116
  throw err;
117
117
  }
118
118
  }
119
- async install(id, host, token, port, start, user, pass, debug) {
119
+ async install(id, host, token, port, start, user, pass) {
120
120
  try {
121
121
  // ensure srv was clear
122
122
  await this.uninstall();
@@ -145,7 +145,6 @@ class AgentWindows extends system_1.default {
145
145
  await this.nssm('set bdy AppRestartDelay 0');
146
146
  await this.nssm('set bdy AppExit Default Restart');
147
147
  await this.nssm('set bdy Start SERVICE_AUTO_START');
148
- await this.nssm(`set bdy AppEnvironmentExtra DEBUG=${debug ? 1 : 0}`);
149
148
  if (user && pass) {
150
149
  await this.nssm(`set bdy ObjectName ${user} ${pass}`);
151
150
  await this.grantAccess(user);
@@ -1154,9 +1154,12 @@ class ApiClient {
1154
1154
  const end = Date.now() + timeout * 1000;
1155
1155
  for (;;) {
1156
1156
  const { apps } = await this.getSandbox(workspace, sandboxId);
1157
+ // A sandbox with no apps has the field omitted altogether, so an empty
1158
+ // list means there is nothing to wait for - not a timeout.
1159
+ const list = apps || [];
1157
1160
  let status = utils_1.SANDBOX_APP_STATUS.RUNNING;
1158
- for (let i = 0; i < apps.length; i += 1) {
1159
- const { app_status } = apps[i];
1161
+ for (let i = 0; i < list.length; i += 1) {
1162
+ const { app_status } = list[i];
1160
1163
  if (app_status === utils_1.SANDBOX_APP_STATUS.FAILED) {
1161
1164
  return utils_1.SANDBOX_APP_STATUS.FAILED;
1162
1165
  }
@@ -128,7 +128,7 @@ const installService = async (options) => {
128
128
  }
129
129
  await output_1.default.spinner(texts_1.TXT_ENABLING_AGENT);
130
130
  try {
131
- await AgentManager.system.install(id, host, agentToken, port, !!options.start, options.user, options.pass, !!options.debug);
131
+ await AgentManager.system.install(id, host, agentToken, port, !!options.start, options.user, options.pass);
132
132
  }
133
133
  catch {
134
134
  await removeAllAndExit(texts_1.ERR_SWW_AGENT_ENABLING, id, host, agentToken);
@@ -17,6 +17,22 @@ commandAgentRun.action(async (options) => {
17
17
  // the user it runs as has an `--app` install of their own
18
18
  AgentManager.system.serviceMode = true;
19
19
  logger_1.default.changeRootPath(AgentManager.system.getNewAgentConfigDir());
20
+ // The level comes from the agent config, which is the only place the two things that
21
+ // change it can write: `agent debug` and the remote DEBUG_ON action both run in a process
22
+ // that cannot rewrite a service definition, so a service reading DEBUG out of its own
23
+ // definition came back from every restart at the level it was installed with.
24
+ //
25
+ // Set unconditionally rather than only when true, so a DEBUG left behind in a definition
26
+ // written by an older cli is overridden by the config instead of being ored with it -
27
+ // otherwise an agent installed with --debug could never be turned back down.
28
+ try {
29
+ logger_1.default.setDebug(!!AgentManager.system.loadSystemConfig().debug);
30
+ }
31
+ catch {
32
+ // Nothing readable to take the level from - no install, or a config this process cannot
33
+ // read or parse. Whatever DEBUG the environment carries stands, which for a service is
34
+ // the one written by the cli that installed it.
35
+ }
20
36
  AgentManager.start(options.id, options.host, options.token, parseInt(options.port, 10));
21
37
  });
22
38
  exports.default = commandAgentRun;
@@ -0,0 +1,18 @@
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 cfg_1 = __importDefault(require("../../tunnel/cfg"));
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const texts_1 = require("../../texts");
9
+ const utils_1 = require("../../utils");
10
+ const commandProjectGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_PROJECT_GET);
11
+ commandProjectGet.action(async () => {
12
+ const project = cfg_1.default.getProject();
13
+ if (!project) {
14
+ output_1.default.exitError(texts_1.TXT_PROJECT_NONE);
15
+ }
16
+ output_1.default.exitNormal(project);
17
+ });
18
+ exports.default = commandProjectGet;
@@ -0,0 +1,31 @@
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 cfg_1 = __importDefault(require("../../tunnel/cfg"));
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const texts_1 = require("../../texts");
9
+ const utils_1 = require("../../utils");
10
+ const input_1 = __importDefault(require("../../input"));
11
+ const commandProjectSet = (0, utils_1.newCommand)('set', texts_1.DESC_COMMAND_PROJECT_SET);
12
+ commandProjectSet.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
+ commandProjectSet.argument('[project]', texts_1.ARG_COMMAND_PROJECT_NAME);
14
+ commandProjectSet.action(async (project, options) => {
15
+ output_1.default.handleSignals();
16
+ const workspace = input_1.default.restApiWorkspace(options.workspace);
17
+ const client = input_1.default.restApiTokenClient();
18
+ if (project) {
19
+ await client.getProject(workspace, project);
20
+ }
21
+ else {
22
+ const response = await client.getProjects(workspace);
23
+ project = await output_1.default.selectProject(response.projects);
24
+ }
25
+ cfg_1.default.setProject(project);
26
+ if (!project)
27
+ output_1.default.exitSuccess(texts_1.TXT_PROJECT_SET_CLEARED);
28
+ else
29
+ output_1.default.exitSuccess((0, texts_1.TXT_PROJECT_SET_SUCCESS)(project));
30
+ });
31
+ exports.default = commandProjectSet;
@@ -22,11 +22,16 @@ commandSandboxAppList.action(async (identifier, options) => {
22
22
  if (!sandbox_id) {
23
23
  output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
24
24
  }
25
- const { apps } = await client.getSandbox(workspace, sandbox_id);
25
+ const sandbox = await client.getSandbox(workspace, sandbox_id);
26
+ // A sandbox with no apps has the field omitted altogether.
27
+ const apps = sandbox.apps || [];
26
28
  if (options.format === 'json') {
27
29
  output_1.default.json(apps);
28
30
  }
29
31
  else {
32
+ if (apps.length === 0) {
33
+ output_1.default.exitError(texts_1.ERR_SANDBOX_APPS_NOT_FOUND);
34
+ }
30
35
  const table = [['ID', 'COMMAND', 'STATUS']];
31
36
  for (const app of apps) {
32
37
  let command = app.command;
@@ -113,8 +113,8 @@ commandSandboxCreate.action(async (options) => {
113
113
  body.first_boot_commands += '\n';
114
114
  body.first_boot_commands += input_1.default.sandboxBootScript(options.bootScript);
115
115
  }
116
- if (options.tags) {
117
- body.tags = options.tags;
116
+ if (options.tag) {
117
+ body.tags = options.tag;
118
118
  }
119
119
  if (options.appCommand) {
120
120
  body.apps = options.appCommand;
@@ -0,0 +1,30 @@
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 input_1 = __importDefault(require("../../../input"));
9
+ const output_1 = __importDefault(require("../../../output"));
10
+ const commandSandboxGetYaml = (0, utils_1.newCommand)('yaml', texts_1.DESC_COMMAND_SANDBOX_GET_YAML);
11
+ commandSandboxGetYaml.hideVersionUpdate = true;
12
+ commandSandboxGetYaml.alias('yml');
13
+ commandSandboxGetYaml.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
14
+ commandSandboxGetYaml.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
15
+ commandSandboxGetYaml.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
16
+ commandSandboxGetYaml.action(async (identifier, options) => {
17
+ const workspace = input_1.default.restApiWorkspace(options.workspace);
18
+ const project = input_1.default.restApiProject(options.project);
19
+ const client = input_1.default.restApiTokenClient();
20
+ let result = await client.listSandboxes(workspace, project);
21
+ const sandboxes = result.sandboxes || [];
22
+ const found = sandboxes.find((s) => s.identifier === identifier);
23
+ if (!found) {
24
+ output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
25
+ }
26
+ const sandboxId = found.id;
27
+ result = await client.getSandboxYaml(workspace, sandboxId);
28
+ output_1.default.exitNormal(Buffer.from(result.yaml, 'base64').toString('utf8'));
29
+ });
30
+ exports.default = commandSandboxGetYaml;