dockup-cli 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,176 +1,169 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var config_exports = {};
30
+ __export(config_exports, {
31
+ clearCurrentWorkspace: () => clearCurrentWorkspace,
32
+ clearToken: () => clearToken,
33
+ default: () => config_default,
34
+ getApiUrl: () => getApiUrl,
35
+ getCurrentWorkspace: () => getCurrentWorkspace,
36
+ getProjectConfig: () => getProjectConfig,
37
+ getToken: () => getToken,
38
+ getUser: () => getUser,
39
+ isLoggedIn: () => isLoggedIn,
40
+ removeProjectConfig: () => removeProjectConfig,
41
+ saveProjectConfig: () => saveProjectConfig,
42
+ setApiUrl: () => setApiUrl,
43
+ setCurrentWorkspace: () => setCurrentWorkspace,
44
+ setToken: () => setToken,
45
+ setUser: () => setUser
17
46
  });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.getToken = getToken;
37
- exports.setToken = setToken;
38
- exports.clearToken = clearToken;
39
- exports.getUser = getUser;
40
- exports.setUser = setUser;
41
- exports.getApiUrl = getApiUrl;
42
- exports.getCurrentWorkspace = getCurrentWorkspace;
43
- exports.setCurrentWorkspace = setCurrentWorkspace;
44
- exports.clearCurrentWorkspace = clearCurrentWorkspace;
45
- exports.setApiUrl = setApiUrl;
46
- exports.getProjectConfig = getProjectConfig;
47
- exports.saveProjectConfig = saveProjectConfig;
48
- exports.removeProjectConfig = removeProjectConfig;
49
- exports.isLoggedIn = isLoggedIn;
50
- const fs = __importStar(require("fs"));
51
- const path = __importStar(require("path"));
52
- const os = __importStar(require("os"));
53
- // Config file path: ~/.dockup/config.json
54
- const CONFIG_DIR = path.join(os.homedir(), '.dockup');
55
- const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
56
- // Ensure config directory exists
47
+ module.exports = __toCommonJS(config_exports);
48
+ var fs = __toESM(require("fs"));
49
+ var path = __toESM(require("path"));
50
+ var os = __toESM(require("os"));
51
+ const CONFIG_DIR = path.join(os.homedir(), ".dockup");
52
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
57
53
  function ensureConfigDir() {
58
- if (!fs.existsSync(CONFIG_DIR)) {
59
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
60
- }
54
+ if (!fs.existsSync(CONFIG_DIR)) {
55
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
56
+ }
61
57
  }
62
- // Read config file
63
58
  function readConfig() {
64
- ensureConfigDir();
65
- if (!fs.existsSync(CONFIG_FILE)) {
66
- return {};
67
- }
68
- try {
69
- return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
70
- }
71
- catch {
72
- return {};
73
- }
74
- }
75
- // Write config file
59
+ ensureConfigDir();
60
+ if (!fs.existsSync(CONFIG_FILE)) {
61
+ return {};
62
+ }
63
+ try {
64
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
65
+ } catch {
66
+ return {};
67
+ }
68
+ }
76
69
  function writeConfig(data) {
77
- ensureConfigDir();
78
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
70
+ ensureConfigDir();
71
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
79
72
  }
80
- // Simple config object that mimics Conf API
81
73
  const config = {
82
- get(key) {
83
- const data = readConfig();
84
- return data[key];
85
- },
86
- set(key, value) {
87
- const data = readConfig();
88
- data[key] = value;
89
- writeConfig(data);
90
- },
91
- delete(key) {
92
- const data = readConfig();
93
- delete data[key];
94
- writeConfig(data);
95
- },
96
- clear() {
97
- writeConfig({});
98
- }
74
+ get(key) {
75
+ const data = readConfig();
76
+ return data[key];
77
+ },
78
+ set(key, value) {
79
+ const data = readConfig();
80
+ data[key] = value;
81
+ writeConfig(data);
82
+ },
83
+ delete(key) {
84
+ const data = readConfig();
85
+ delete data[key];
86
+ writeConfig(data);
87
+ },
88
+ clear() {
89
+ writeConfig({});
90
+ }
99
91
  };
100
- // Get API token
101
92
  function getToken() {
102
- return config.get('token');
93
+ return config.get("token");
103
94
  }
104
- // Set API token
105
95
  function setToken(token) {
106
- config.set('token', token);
96
+ config.set("token", token);
107
97
  }
108
- // Clear token (logout)
109
98
  function clearToken() {
110
- config.delete('token');
111
- config.delete('user');
112
- config.delete('currentWorkspace');
99
+ config.delete("token");
100
+ config.delete("user");
101
+ config.delete("currentWorkspace");
113
102
  }
114
- // Get current user
115
103
  function getUser() {
116
- return config.get('user');
104
+ return config.get("user");
117
105
  }
118
- // Set current user
119
106
  function setUser(user) {
120
- config.set('user', user);
107
+ config.set("user", user);
121
108
  }
122
- // Get API URL
123
109
  function getApiUrl() {
124
- return config.get('apiUrl') || 'https://app.dockup.ai/api';
110
+ return config.get("apiUrl") || "https://app.dockup.ai/api";
125
111
  }
126
- // Get current workspace
127
112
  function getCurrentWorkspace() {
128
- return config.get('currentWorkspace');
113
+ return config.get("currentWorkspace");
129
114
  }
130
- // Set current workspace
131
115
  function setCurrentWorkspace(workspace) {
132
- config.set('currentWorkspace', workspace);
116
+ config.set("currentWorkspace", workspace);
133
117
  }
134
- // Clear current workspace
135
118
  function clearCurrentWorkspace() {
136
- config.delete('currentWorkspace');
119
+ config.delete("currentWorkspace");
137
120
  }
138
- // Set API URL (for development)
139
121
  function setApiUrl(url) {
140
- config.set('apiUrl', url);
122
+ config.set("apiUrl", url);
141
123
  }
142
- // Project-level config stored in .dockup file
143
- const PROJECT_CONFIG_FILE = '.dockup';
144
- // Get project config from current directory
124
+ const PROJECT_CONFIG_FILE = ".dockup";
145
125
  function getProjectConfig() {
146
- const configPath = path.join(process.cwd(), PROJECT_CONFIG_FILE);
147
- if (!fs.existsSync(configPath)) {
148
- return null;
149
- }
150
- try {
151
- const content = fs.readFileSync(configPath, 'utf-8');
152
- return JSON.parse(content);
153
- }
154
- catch {
155
- return null;
156
- }
157
- }
158
- // Save project config to current directory
126
+ const configPath = path.join(process.cwd(), PROJECT_CONFIG_FILE);
127
+ if (!fs.existsSync(configPath)) {
128
+ return null;
129
+ }
130
+ try {
131
+ const content = fs.readFileSync(configPath, "utf-8");
132
+ return JSON.parse(content);
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
159
137
  function saveProjectConfig(projectConfig) {
160
- const configPath = path.join(process.cwd(), PROJECT_CONFIG_FILE);
161
- fs.writeFileSync(configPath, JSON.stringify(projectConfig, null, 2));
138
+ const configPath = path.join(process.cwd(), PROJECT_CONFIG_FILE);
139
+ fs.writeFileSync(configPath, JSON.stringify(projectConfig, null, 2));
162
140
  }
163
- // Remove project config
164
141
  function removeProjectConfig() {
165
- const configPath = path.join(process.cwd(), PROJECT_CONFIG_FILE);
166
- if (fs.existsSync(configPath)) {
167
- fs.unlinkSync(configPath);
168
- return true;
169
- }
170
- return false;
171
- }
172
- // Check if logged in
173
- function isLoggedIn() {
174
- return !!getToken();
142
+ const configPath = path.join(process.cwd(), PROJECT_CONFIG_FILE);
143
+ if (fs.existsSync(configPath)) {
144
+ fs.unlinkSync(configPath);
145
+ return true;
146
+ }
147
+ return false;
175
148
  }
176
- exports.default = config;
149
+ function isLoggedIn() {
150
+ return !!getToken();
151
+ }
152
+ var config_default = config;
153
+ // Annotate the CommonJS export names for ESM import in node:
154
+ 0 && (module.exports = {
155
+ clearCurrentWorkspace,
156
+ clearToken,
157
+ getApiUrl,
158
+ getCurrentWorkspace,
159
+ getProjectConfig,
160
+ getToken,
161
+ getUser,
162
+ isLoggedIn,
163
+ removeProjectConfig,
164
+ saveProjectConfig,
165
+ setApiUrl,
166
+ setCurrentWorkspace,
167
+ setToken,
168
+ setUser
169
+ });
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var flags_exports = {};
20
+ __export(flags_exports, {
21
+ isJson: () => isJson,
22
+ setJson: () => setJson
23
+ });
24
+ module.exports = __toCommonJS(flags_exports);
25
+ let _json = false;
26
+ function setJson(value) {
27
+ _json = value;
28
+ }
29
+ function isJson() {
30
+ return _json;
31
+ }
32
+ // Annotate the CommonJS export names for ESM import in node:
33
+ 0 && (module.exports = {
34
+ isJson,
35
+ setJson
36
+ });
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var output_exports = {};
30
+ __export(output_exports, {
31
+ emit: () => emit,
32
+ fail: () => fail,
33
+ ok: () => ok
34
+ });
35
+ module.exports = __toCommonJS(output_exports);
36
+ var import_chalk = __toESM(require("chalk"));
37
+ var import_flags = require("./flags");
38
+ function emit(data, human) {
39
+ if ((0, import_flags.isJson)()) {
40
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
41
+ } else {
42
+ human();
43
+ }
44
+ }
45
+ function fail(message, extra = {}) {
46
+ if ((0, import_flags.isJson)()) {
47
+ process.stdout.write(JSON.stringify({ ok: false, error: message, ...extra }, null, 2) + "\n");
48
+ } else {
49
+ console.error(import_chalk.default.red(message));
50
+ }
51
+ process.exit(1);
52
+ }
53
+ function ok(data, human) {
54
+ if ((0, import_flags.isJson)()) {
55
+ process.stdout.write(JSON.stringify({ ok: true, ...data }, null, 2) + "\n");
56
+ } else {
57
+ human();
58
+ }
59
+ }
60
+ // Annotate the CommonJS export names for ESM import in node:
61
+ 0 && (module.exports = {
62
+ emit,
63
+ fail,
64
+ ok
65
+ });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var target_exports = {};
20
+ __export(target_exports, {
21
+ resolveTarget: () => resolveTarget
22
+ });
23
+ module.exports = __toCommonJS(target_exports);
24
+ var import_config = require("./config");
25
+ function resolveTarget(arg) {
26
+ if (arg && arg.includes("/")) {
27
+ const [projectSlug, serviceSlug] = arg.split("/");
28
+ if (projectSlug && serviceSlug) return { projectSlug, serviceSlug };
29
+ }
30
+ if (arg && !arg.includes("/")) {
31
+ throw new Error(`Invalid target "${arg}". Use the form "project/service".`);
32
+ }
33
+ const cfg = (0, import_config.getProjectConfig)();
34
+ if (cfg) return { projectSlug: cfg.projectSlug, serviceSlug: cfg.serviceSlug };
35
+ throw new Error('No target. Pass "project/service" or run `dockup link` in the project directory.');
36
+ }
37
+ // Annotate the CommonJS export names for ESM import in node:
38
+ 0 && (module.exports = {
39
+ resolveTarget
40
+ });
package/dockupcli.md ADDED
@@ -0,0 +1,185 @@
1
+ # Dockup CLI — Guide for AI Agents
2
+
3
+ `dockup` is a command-line tool for the Dockup deploy platform. It lets an agent
4
+ inspect and manage deployed services entirely through **bash**: read/write env
5
+ vars, check deploy status, browse deployment history, read logs, and trigger
6
+ deploys.
7
+
8
+ **Golden rule for agents:** pass `--json` (or `-j`) to **every** command. You get
9
+ structured JSON on stdout; on error you get `{"ok":false,"error":"...","code":"..."}`
10
+ and a non-zero exit code. Without `--json` you get human-formatted text.
11
+
12
+ ---
13
+
14
+ ## Install & auth
15
+
16
+ ```bash
17
+ npm install -g dockup-cli # or: node /path/to/cli/dist/index.js
18
+ dockup login # opens app.dockup.ai/cli-auth → paste the long-lived token
19
+ dockup login -t <TOKEN> # non-interactive (agents): pass the token directly
20
+ dockup whoami --json # verify auth → {"id","email","name"}
21
+ ```
22
+
23
+ The token is stored in `~/.dockup/config.json` and never expires.
24
+
25
+ ---
26
+
27
+ ## Targeting a service
28
+
29
+ Most commands take an optional **`project/service`** argument (both are slugs):
30
+
31
+ ```bash
32
+ dockup info my-project/my-api --json
33
+ ```
34
+
35
+ If omitted, the CLI uses the **linked service** from a `.dockup` file in the
36
+ current directory (created by `dockup link`). For env commands the target is the
37
+ `-s, --service <project/service>` flag instead of a positional arg.
38
+
39
+ Discover valid targets with `dockup services --json` (the `target` field is
40
+ exactly the `project/service` string to use everywhere else).
41
+
42
+ ---
43
+
44
+ ## Commands
45
+
46
+ ### Discovery
47
+
48
+ ```bash
49
+ dockup services --json
50
+ # → {"count":N,"services":[{"target":"proj/svc","name","project","status","domain","branch","deploymentType","countryCode"}]}
51
+
52
+ dockup info my-project/my-api --json
53
+ # → {"target","name","status","deploymentType","repoUrl","branch","domain","port",
54
+ # "exposedPort","autoDeployEnabled","prPreviewEnabled","memoryLimit","cpuLimit",
55
+ # "lastDeployedAt","lastError","env":[{"key","isSecret","value"}],"customDomains":[...],
56
+ # "latestDeployment":{...}}
57
+ ```
58
+
59
+ ### Status & history
60
+
61
+ ```bash
62
+ dockup status my-project/my-api --json
63
+ # → {"target","status","domain","lastDeployedAt","lastError","latestDeployment":{...}}
64
+
65
+ dockup deployments my-project/my-api -n 20 --json
66
+ # (alias: dockup history)
67
+ # → {"target","count","deployments":[{"id","status","commitHash","commitMessage",
68
+ # "triggeredBy","startedAt","completedAt","createdAt"}]}
69
+ ```
70
+
71
+ `status` values: `creating | building | deploying | running | stopped | suspended | error`.
72
+ deployment `status`: `pending | cloning | building | deploying | success | failed`.
73
+
74
+ ### Logs
75
+
76
+ ```bash
77
+ dockup logs my-project/my-api --json # runtime logs (running container/pods)
78
+ dockup logs my-project/my-api -n 500 --json # more lines
79
+ dockup logs my-project/my-api --build --json # build logs of the latest deployment
80
+ # runtime → {"target","type":"runtime","logs":"..."}
81
+ # build → {"target","type":"build","deployment":{...},"lastError":...,"logs":"..."}
82
+ ```
83
+
84
+ When a deploy fails, read `--build` logs to see why.
85
+
86
+ ### Environment variables
87
+
88
+ Secret **values are never returned** (shown as `null` / `<secret>`); keys and the
89
+ `isSecret` flag are. You can still set/overwrite secrets.
90
+
91
+ ```bash
92
+ dockup env list -s my-project/my-api --json
93
+ # → {"target","count","env":[{"key","isSecret","value"}]}
94
+
95
+ dockup env set DATABASE_URL=postgres://... -s my-project/my-api --json
96
+ dockup env set API_KEY=sk-123 --secret -s my-project/my-api --json
97
+ dockup env remove OLD_VAR -s my-project/my-api --json
98
+ dockup env import .env -s my-project/my-api --json # bulk from a .env-style file
99
+ # mutations → {"ok":true,"target","key"/"keys","hint":"redeploy to apply"}
100
+ ```
101
+
102
+ > Env changes only take effect after a redeploy.
103
+
104
+ ### Deploy
105
+
106
+ ```bash
107
+ dockup deploy my-project/my-api --json # trigger deploy (no git push)
108
+ dockup deploy my-project/my-api -b main --json
109
+ # → {"ok":true,"target","deploymentId","hint":"poll `dockup status` for progress"}
110
+
111
+ # From inside a linked git repo, git push + deploy + live progress:
112
+ dockup push
113
+ ```
114
+
115
+ After triggering, poll `dockup status ... --json` until
116
+ `latestDeployment.status` is `success` or `failed`.
117
+
118
+ ### Private registry (private FROM base image)
119
+
120
+ If your Dockerfile starts with `FROM <private-registry>/...` (e.g. a private
121
+ GHCR base image), set credentials so the build host can pull it. The token is
122
+ stored encrypted and never returned by the API.
123
+
124
+ ```bash
125
+ dockup registry set my-project/my-api \
126
+ --url ghcr.io --user my-org --token "$GHCR_PAT" --json
127
+ # → {"ok":true,"target","registryUrl","registryUsername","hint":"redeploy to use the private base image"}
128
+
129
+ dockup registry clear my-project/my-api --json
130
+ ```
131
+
132
+ - `--url` is the registry host (`ghcr.io`, `registry.example.com`, …).
133
+ - For GHCR, a PAT with `read:packages` scope is enough.
134
+ - Credentials apply on the next deploy (build runs `docker login` then `docker build`).
135
+ - `dockup info --json` shows `registryUrl`, `registryUsername` and
136
+ `hasRegistryAuth` (the password is never exposed).
137
+
138
+ ---
139
+
140
+ ## Agent recipes
141
+
142
+ **Find a service and check if it's healthy**
143
+ ```bash
144
+ dockup services --json | jq '.services[] | select(.name=="my-api")'
145
+ dockup status my-project/my-api --json | jq -r '.status'
146
+ ```
147
+
148
+ **Deploy and wait for the result**
149
+ ```bash
150
+ dockup deploy my-project/my-api --json
151
+ while :; do
152
+ s=$(dockup status my-project/my-api --json | jq -r '.latestDeployment.status')
153
+ [ "$s" = "success" ] && { echo "deployed"; break; }
154
+ [ "$s" = "failed" ] && { dockup logs my-project/my-api --build --json | jq -r '.logs' | tail -40; exit 1; }
155
+ sleep 3
156
+ done
157
+ ```
158
+
159
+ **Update config then redeploy**
160
+ ```bash
161
+ dockup env set FEATURE_FLAG=on -s my-project/my-api --json
162
+ dockup deploy my-project/my-api --json
163
+ ```
164
+
165
+ **Diagnose a crash**
166
+ ```bash
167
+ dockup status my-project/my-api --json | jq '.lastError'
168
+ dockup logs my-project/my-api --json | jq -r '.logs' | tail -60 # runtime
169
+ dockup logs my-project/my-api --build --json | jq -r '.logs' | tail -60 # build
170
+ ```
171
+
172
+ ---
173
+
174
+ ## Error handling
175
+
176
+ - Every command exits non-zero on failure.
177
+ - With `--json`, failures print `{"ok":false,"error":"<message>","code":"<code>"}`.
178
+ Codes include: `not_logged_in`, `no_target`, `bad_format`, `file_not_found`.
179
+ - 404 "not found" usually means a wrong `project/service` slug — re-run
180
+ `dockup services --json` to get exact targets.
181
+
182
+ ## Notes & limits
183
+ - `dockup db ...` and `dockup workspace rename/delete` are **not reliable** yet
184
+ (CLI/server endpoint mismatch) — avoid them in automation.
185
+ - Pretty (non-JSON) output is for humans; agents should always use `--json`.