@deepdockai/cli 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.5
4
+
5
+ - The agent skill's clients: `systems()` is typed as the list of systems the API answers, and a paired `Device` lists its systems as objects, with `outdated` and a `claimedAt` that can be `null`.
6
+ - The TypeScript client runs with plain `node file.ts` (Node 22.18+, or 22.13+ with `--experimental-strip-types`).
7
+ - Both clients take their base URL from `DEEPDOCK_BASE_URL` when it is set, like the CLI.
8
+ - The agent skill holds nothing specific to one system: the recipe for one system is gone, and the skill points to the system's behaviour notes in its OpenAPI document instead.
9
+
10
+ ## 1.0.4
11
+
12
+ - `DEEPDOCK_CONFIG_DIR` moves the settings file (and only it) to a directory of your choice, on every platform; it must be an absolute path. Useful for keeping separate sign-ins apart, for example one per project or account.
13
+
3
14
  ## 1.0.3
4
15
 
5
16
  - `auth logout --keep-keys` revokes only the sign-in. On a terminal, plain `auth logout` lists the API keys and asks before revoking them (default: keep); with `--json`, `--yes` or no terminal it still revokes them. `keys create` says so.
@@ -17,7 +28,7 @@
17
28
  ## 1.0.1
18
29
 
19
30
  - The agent skill: page with `nextCursor` until it is `null` (a short page can carry a cursor), and cover the codes `access_outdated`, `access_missing`, `record_locked`, `record_not_editable`, `unknown_provider`, `payload_too_large` and the system status `unknown`, in the skill and the CLI's exit-code table.
20
- - The agent skill: one statement on how to fetch the OpenAPI document, and a MEDICAL OFFICE recipe with its action names.
31
+ - The agent skill: one statement on how to fetch the OpenAPI document, and a recipe for one system with its action names.
21
32
  - The agent skill states the id rule in full: reads and creates answer ids as integers, a create's new id included; every id you send is a string of digits without a leading zero, at most 10 characters, in a query string and a write body alike.
22
33
 
23
34
  ## 1.0.0
package/README.md CHANGED
@@ -236,12 +236,13 @@ macOS and Linux need nothing special. On Windows:
236
236
  revoked, and `data.alreadyRevoked` says so). `auth logout` reports the failure in
237
237
  `apiKeysRevoked[].removed` and in `meta.hint`, and clears its local records either way.
238
238
  - **Local state** lives in `$XDG_CONFIG_HOME/deepdock/config.json`, else
239
- `~/.config/deepdock/config.json` (`%APPDATA%\deepdock\config.json` on Windows), mode 600 in a
240
- mode 700 directory. It holds the sign-in, the default organisation, the ids of the keys this
239
+ `~/.config/deepdock/config.json` (`%APPDATA%\deepdock\config.json` on Windows), or in
240
+ `$DEEPDOCK_CONFIG_DIR/config.json` when that absolute path is set; mode 600, in a directory the
241
+ CLI creates mode 700 when it does not exist. It holds the sign-in, the default organisation, the ids of the keys this
241
242
  CLI created (never their values) and a pending approval request. A file other users can read
242
243
  is refused with the `chmod` to run.
243
244
  - **Environment:** `DEEPDOCK_ORG`, `DEEPDOCK_BASE_URL` (https, or http for localhost only),
244
- `NO_COLOR`, `FORCE_COLOR` (colours messages on a stream that is not a terminal; `NO_COLOR` wins). A sign-in or pending request is never sent to a different base URL than the one it
245
+ `DEEPDOCK_CONFIG_DIR`, `NO_COLOR`, `FORCE_COLOR` (colours messages on a stream that is not a terminal; `NO_COLOR` wins). A sign-in or pending request is never sent to a different base URL than the one it
245
246
  was made against.
246
247
 
247
248
  Report a vulnerability by email to torben@deepdock.ai, not in a public issue: [SECURITY.md](SECURITY.md).
@@ -256,11 +257,33 @@ bun run test:node # the same tests under Node (needs dist/ from the build)
256
257
  bun run format && bun run lint && bun run typecheck && bun run check:public
257
258
  ```
258
259
 
259
- `tests/fakeApi.ts` is a local HTTP server answering the routes the CLI uses:
260
+ ### Trying a change locally
261
+
262
+ `bun run dev` runs the CLI from source. Each API gets a settings directory of its own under
263
+ `<config dir>/deepdock-dev/<api host>/`, so a sign-in made against one API never sits next to
264
+ another's; only the CLI's own settings move (`DEEPDOCK_CONFIG_DIR`), nothing else on the machine:
265
+
266
+ ```bash
267
+ bun run dev -- auth status # the production API
268
+ DEEPDOCK_BASE_URL=http://localhost:8787 bun run dev -- auth login --no-wait
269
+ ```
270
+
271
+ The script does not depend on the current directory: from elsewhere, call it by its path
272
+ (`bun /path/to/devkit/scripts/dev.ts ...`), or put the `DEEPDOCK_BASE_URL=... bun .../dev.ts` line
273
+ in a shell alias per API. To run the exact tarball npm would publish, from any directory:
274
+
275
+ ```bash
276
+ bun run pack:try -- auth status # builds, packs, prints the one-line command to run it
277
+ ```
278
+
279
+ It prints `DEEPDOCK_BASE_URL=... DEEPDOCK_CONFIG_DIR=... npx -y --package=/path/to/deepdockai-cli-<version>.tgz deepdock auth status`,
280
+ for the same API and settings directory `bun run dev` would use (`npx <tarball>` alone would try
281
+ to execute the archive). `tests/fakeApi.ts` is a local stand-in that approves every request at
282
+ once:
260
283
 
261
284
  ```bash
262
285
  bun scripts/fakeServer.ts & # prints its URL
263
- DEEPDOCK_BASE_URL=http://127.0.0.1:<port> node dist/deepdock.js auth login
286
+ DEEPDOCK_BASE_URL=http://127.0.0.1:<port> bun run dev -- auth login
264
287
  ```
265
288
 
266
289
  ## Licence
package/dist/deepdock.js CHANGED
@@ -11424,7 +11424,7 @@ import { spawn } from "node:child_process";
11424
11424
  import { homedir, hostname } from "node:os";
11425
11425
 
11426
11426
  // src/version.ts
11427
- var VERSION = "1.0.3";
11427
+ var VERSION = "1.0.5";
11428
11428
 
11429
11429
  // src/runtime.ts
11430
11430
  var DEFAULT_BASE_URL = "https://api.deepdock.ai";
@@ -11714,17 +11714,21 @@ var renameWithRetry = async (from, to) => {
11714
11714
  // src/config/store.ts
11715
11715
  var PENDING_KEPT_MS = 60 * 60 * 1000;
11716
11716
  var emptyConfig = () => ({ keys: [], pending: [] });
11717
- var configPath = (rt) => {
11718
- if (rt.platform === "win32") {
11719
- const base = rt.env.APPDATA ?? join(rt.homedir, "AppData", "Roaming");
11720
- return join(base, "deepdock", "config.json");
11721
- }
11722
- const xdg = rt.env.XDG_CONFIG_HOME;
11723
- if (xdg && !isAbsolute(xdg)) {
11724
- throw usageError("XDG_CONFIG_HOME must be an absolute path.", "Unset it or give a full path.");
11717
+ var absoluteDir = (env, name) => {
11718
+ const value = env[name];
11719
+ if (value && !isAbsolute(value)) {
11720
+ throw usageError(`${name} must be an absolute path.`, "Unset it or give a full path.");
11725
11721
  }
11726
- const base = xdg || join(rt.homedir, ".config");
11727
- return join(base, "deepdock", "config.json");
11722
+ return value || undefined;
11723
+ };
11724
+ var configBaseDir = (rt) => {
11725
+ if (rt.platform === "win32")
11726
+ return rt.env.APPDATA || join(rt.homedir, "AppData", "Roaming");
11727
+ return absoluteDir(rt.env, "XDG_CONFIG_HOME") ?? join(rt.homedir, ".config");
11728
+ };
11729
+ var configPath = (rt) => {
11730
+ const own = absoluteDir(rt.env, "DEEPDOCK_CONFIG_DIR");
11731
+ return own ? join(own, "config.json") : join(configBaseDir(rt), "deepdock", "config.json");
11728
11732
  };
11729
11733
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11730
11734
  var text = (value) => typeof value === "string" ? value : undefined;
@@ -12079,7 +12083,7 @@ Examples
12079
12083
 
12080
12084
  More
12081
12085
  Exit codes: \`deepdock help exit-codes\`. API reference per system: \`deepdock systems openapi <system>\`.
12082
- Environment: DEEPDOCK_ORG, DEEPDOCK_BASE_URL, NO_COLOR, FORCE_COLOR.
12086
+ Environment: DEEPDOCK_ORG, DEEPDOCK_BASE_URL, DEEPDOCK_CONFIG_DIR, NO_COLOR, FORCE_COLOR.
12083
12087
  Actions run with an API key from your own code, never from this CLI: \`deepdock keys create --help\`.
12084
12088
  `;
12085
12089
  var AUTH_LOGIN_HELP = `Sign in to Deepdock as yourself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepdockai/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Deepdock partner CLI: pair devices, see systems and actions, create the API key your code runs them with.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,6 +23,8 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "bun scripts/build.ts",
26
+ "dev": "bun scripts/dev.ts",
27
+ "pack:try": "bun scripts/packTry.ts",
26
28
  "format": "biome format .",
27
29
  "format:fix": "biome format --write .",
28
30
  "lint": "biome lint .",
package/skill/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: deepdock-integration
3
- description: Build, debug or test an integration with Deepdock's partner API, the REST actions under /v1 that read and write records in a customer's on-premise business system (for example MEDICAL OFFICE) through a paired Deepdock device. Use when code calls api.deepdock.ai, handles ddk_ API keys, pairs a device by code, calls a system action, handles error codes such as device_offline, action_paused, idempotency_conflict or outcome_unknown, or runs the deepdock CLI.
3
+ description: Build, debug or test an integration with Deepdock's partner API, the REST actions under /v1 that read and write records in a customer's on-premise business system through a paired Deepdock device. Use when code calls api.deepdock.ai, handles ddk_ API keys, pairs a device by code, calls a system action, handles error codes such as device_offline, action_paused, idempotency_conflict or outcome_unknown, or runs the deepdock CLI.
4
4
  metadata:
5
5
  cli: "@deepdockai/cli"
6
6
  ---
@@ -58,7 +58,8 @@ and what your code does with the result.
58
58
  dashboard. The CLI receives the key once, writes `DEEPDOCK_API_KEY=<key>` into that file (it
59
59
  must be ignored by git) and keeps no copy. It is displayed to you or to the human by nobody.
60
60
  You work only with the key's id, prefix and the file name. Never ask for, read, print or paste
61
- a key value.
61
+ a key value. If the CLI runs with `DEEPDOCK_BASE_URL` set (a non-production API), add the same
62
+ `DEEPDOCK_BASE_URL=<url>` line to that file ([auth-and-keys.md](references/auth-and-keys.md)).
62
63
  6. **Test it.** Run your own code against the demo device the human named, with the key from
63
64
  `.env.local`, and read back with the read action. The CLI runs no action. Read
64
65
  [references/testing.md](references/testing.md) before any write.
@@ -67,6 +68,9 @@ and what your code does with the result.
67
68
 
68
69
  ## Rules that are not in the OpenAPI document
69
70
 
71
+ - **Read the system's behaviour notes first.** Before the first write, read the system's behaviour
72
+ notes in its OpenAPI document (each action's `description`): what it needs (an author, an open
73
+ case), what it accepts and what cannot be undone. Nothing system-specific lives in this skill.
70
74
  - **Discover, never hard-code.** Field names, patterns, enums and lengths come from the live OpenAPI
71
75
  document (`deepdock systems openapi <system>`, or `GET /v1/systems/{system}/openapi.json` with
72
76
  your key), never from memory or from this skill. The document lists Deepdock's whole catalogue for the system; your organisation's assigned
@@ -34,6 +34,9 @@ in the dashboard" and stop retrying.
34
34
  - Read it from the environment or a secret store (`DEEPDOCK_API_KEY` is the convention the
35
35
  dashboard's examples use). Never a literal in source, a test fixture, a log or a URL.
36
36
  - One key per deployment is enough; there is no per-device key.
37
+ - A key works only against the API it was created on. The CLI uses production unless
38
+ `DEEPDOCK_BASE_URL` is set; when it is (`deepdock auth status --json` shows it as `baseUrl`), put
39
+ the same `DEEPDOCK_BASE_URL=<url>` line next to the key. The bundled clients read it too.
37
40
  - Getting a key into the app is the CLI's job: `deepdock keys create <name> --expires <d>
38
41
  --write-env <file>` opens a link to the dashboard; a human clicks Approve (or Deny); the CLI
39
42
  receives the key once and writes `DEEPDOCK_API_KEY=<key>` into the file, which must be ignored
@@ -71,35 +71,14 @@ of a customer, the note id of an update. The pattern is always the same:
71
71
 
72
72
  Never invent an id, copy one from another device, or hard-code one from a test run.
73
73
 
74
- ## Recipe: MEDICAL OFFICE (`medical-office`)
75
-
76
- The examples above use the placeholder system `example-erp`. For a practice running MEDICAL OFFICE
77
- the same patterns use real actions (the live OpenAPI document is still the authority for fields):
78
-
79
- 1. `readUsers` lists the users who can author an entry; pass one id as `authorUserId`.
80
- 2. `searchPatients?lastName=...` finds a patient: a prefix match with umlauts spelled out
81
- (`Müller` and `Mueller` match, `Muller` does not), answering `patientId`, `lastName`,
82
- `firstName`, `birthDate` (`yyyy-mm-dd`, `null` when the system holds no readable date).
83
- 3. `readCases` lists the patient's cases; a write that needs an open case (`createDiagnosis` and
84
- the note actions) refuses with `422 no_open_case` when there is none, or takes a `caseId`.
85
- `readProviders` lists the providers for `providerId` (`422 unknown_provider` otherwise).
86
- 4. Write with `createDiagnosis` (and the other create actions) under a fresh `Idempotency-Key`,
87
- then read back.
88
-
89
- Things the schemas show but are easy to get wrong:
74
+ ## Easy to get wrong
90
75
 
91
76
  - **Ids come back as integers and go in as strings.** A read and a create answer ids as integers
92
- (a create's new `entryId` or `diagnosisId` too); only an update echoes the key you sent, as the
93
- string you sent. `patientId: 1001` in a search result goes back as `patientId=1001` in a read's
94
- query string and as `"patientId": "1001"` in a write body: digits only, no leading zero, at most
95
- 10 characters.
96
- - **Nothing is undone through the API.** No action deletes or reverts a record; a write you
97
- regret stays until someone removes it in MEDICAL OFFICE itself. Write only what you mean to keep.
98
- - **Mark every test text `TEST-DEEPDOCK-<id>`** (a diagnosis text, a note), on the demo device
99
- only, and list what you created ([testing.md](testing.md)).
100
- - Text fields accept MEDICAL OFFICE's Windows-1252 repertoire only; anything else (a tab or line
101
- break outside a note) answers `422 invalid_input` before a write. Note actions take a `format` of `text` (default, stored as
102
- typed) or `markdown`.
77
+ (a create's new id too); only an update echoes the key you sent, as the string you sent.
78
+ `customerId: 1001` in a search result goes back as `customerId=1001` in a read's query string and
79
+ as `"customerId": "1001"` in a write body: digits only, no leading zero, at most 10 characters.
80
+ - **Mark every test text `TEST-DEEPDOCK-<id>`** on the demo device only, and list what you created
81
+ ([testing.md](testing.md)).
103
82
  - A read action's query string treats `+` as a space: send a literal `+` as `%2B`.
104
83
 
105
84
  ## Timeouts and latency
@@ -84,7 +84,7 @@ A failure is one document on stderr: the API's error object unchanged plus what
84
84
  | 0 | done | |
85
85
  | 1 | something on the command line to fix | `invalid_usage` (a bad flag, an unknown slug, a question with no terminal and no `--yes`, an env file git does not ignore), `org_required` |
86
86
  | 2 | the API refused the request; do not repeat it unchanged | `invalid_request`, `not_found`, `device_not_found`, `pairing_code_invalid`, `other_partner_build`, `rate_limited`, `forbidden`, `conflict` and the other refusals (not `access_outdated` and `access_missing`, see exit 3) |
87
- | 3 | retry later (`access_outdated` and `access_missing`: after the practice repeats "Erneut einrichten" in the Deepdock client) | `device_offline`, `action_paused`, `access_outdated`, `access_missing`, `system_unavailable`, `target_busy`, `temporarily_unavailable`, `internal_error`, `connection_lost` |
87
+ | 3 | retry later (`access_outdated` and `access_missing`: after the customer repeats "Erneut einrichten" in the Deepdock client) | `device_offline`, `action_paused`, `access_outdated`, `access_missing`, `system_unavailable`, `target_busy`, `temporarily_unavailable`, `internal_error`, `connection_lost` |
88
88
  | 4 | the CLI is too old for the API: run `npx @deepdockai/cli@latest` | `client_outdated` |
89
89
  | 5 | sign-in needed: run `deepdock auth login` | `sign_in_needed`, `approval_denied`, `approval_expired` |
90
90
 
@@ -7,6 +7,7 @@ Everything action-specific (paths, fields, methods) comes from the live OpenAPI
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import os
10
11
  from dataclasses import dataclass, field, replace
11
12
  from typing import Any, Dict, Iterator, List, Optional, Tuple
12
13
  from urllib.parse import quote
@@ -43,9 +44,11 @@ class WriteResult:
43
44
 
44
45
 
45
46
  class Deepdock:
46
- def __init__(self, api_key: str, base: str = "https://api.deepdock.ai", timeout: float = 30.0,
47
+ def __init__(self, api_key: str, base: Optional[str] = None, timeout: float = 30.0,
47
48
  client: Optional[httpx.Client] = None):
48
49
  # 30 s is above Deepdock's 20 s device wait, so device_offline arrives before a client timeout.
50
+ # Default: DEEPDOCK_BASE_URL from the environment (the CLI's own variable), else production.
51
+ base = base or os.environ.get("DEEPDOCK_BASE_URL") or "https://api.deepdock.ai"
49
52
  self.c = client or httpx.Client(base_url=base.rstrip("/"), timeout=timeout)
50
53
  self.c.headers["Authorization"] = f"Bearer {api_key}"
51
54
 
@@ -98,7 +101,7 @@ class Deepdock:
98
101
  headers={"Idempotency-Key": idempotency_key})
99
102
  return WriteResult(data=data, replayed=headers.get("Idempotent-Replayed") == "true")
100
103
 
101
- def systems(self, device_id: str) -> Dict[str, Any]:
104
+ def systems(self, device_id: str) -> List[Dict[str, Any]]:
102
105
  """The device's systems and each action's state. A live call: can answer device_offline."""
103
106
  data, _ = self._send("GET", f"/v1/devices/{quote(device_id, safe='')}/systems")
104
107
  return data
@@ -6,16 +6,21 @@
6
6
  * Everything action-specific (paths, fields, methods) comes from the live OpenAPI document.
7
7
  */
8
8
 
9
+ // Plain fields, no constructor parameter properties: Node runs this file with its default type
10
+ // stripping, which refuses syntax that needs a transform.
9
11
  export class DeepdockError extends Error {
10
- constructor(
11
- readonly status: number,
12
- readonly code: string,
13
- message: string,
14
- readonly requestId?: string,
15
- readonly systems?: string[],
16
- ) {
12
+ readonly status: number
13
+ readonly code: string
14
+ readonly requestId?: string
15
+ readonly systems?: string[]
16
+
17
+ constructor(status: number, code: string, message: string, requestId?: string, systems?: string[]) {
17
18
  super(message)
18
19
  this.name = 'DeepdockError'
20
+ this.status = status
21
+ this.code = code
22
+ this.requestId = requestId
23
+ this.systems = systems
19
24
  }
20
25
  }
21
26
 
@@ -34,6 +39,10 @@ export type Page<Item> = { items: Item[]; nextCursor: string | null }
34
39
  export type WriteResult<T> = { data: T; replayed: boolean }
35
40
  export type WriteMethod = 'POST' | 'PATCH'
36
41
 
42
+ export type SystemStatus = 'running' | 'stopped' | 'missing' | 'unknown'
43
+ /** The database access the device last reported; `null` while none is known. */
44
+ export type SystemAccess = 'ok' | 'outdated' | 'missing' | 'not_set_up' | null
45
+
37
46
  export type Device = {
38
47
  id: string
39
48
  label: string | null
@@ -42,38 +51,55 @@ export type Device = {
42
51
  clientVersion: string
43
52
  status: 'connected' | 'offline' | 'inactive'
44
53
  lastSeenAt: string | null
45
- claimedAt: string
46
- systems: string[]
54
+ claimedAt: string | null
55
+ /** True when an action assigned to you needs a newer client: that action answers device_outdated. */
56
+ outdated: boolean
47
57
  updateAvailable?: string
58
+ systems: { system: string; version: string | null; status: SystemStatus; access: SystemAccess; reportedAt: string }[]
48
59
  }
49
60
 
50
61
  export type ActionState =
51
- | { state: 'active' }
52
- | { state: 'paused'; reason: string; pausedAt: string }
62
+ | { action: string; state: 'active' }
63
+ | { action: string; state: 'paused'; reason: 'structure_changed' | 'check_failed'; pausedAt: string }
53
64
 
54
65
  export type DeviceSystem = {
66
+ system: string
55
67
  label: string | null
56
- version: string
57
- status: 'running' | 'stopped' | 'missing'
58
- actions: Record<string, ActionState>
68
+ version?: string
69
+ status: SystemStatus
70
+ access: SystemAccess
71
+ actions: ActionState[]
59
72
  }
60
73
 
61
74
  export type DeepdockOptions = {
75
+ /** Default: `DEEPDOCK_BASE_URL` from the environment (the CLI's own variable), else production. */
62
76
  base?: string
63
77
  /** Above Deepdock's 20 s device wait, so you receive device_offline instead of timing out first. */
64
78
  timeoutMs?: number
65
79
  fetch?: typeof fetch
66
80
  }
67
81
 
82
+ // `process` is absent in a browser, and Deno throws on `process.env` without --allow-env; then the
83
+ // base comes from the options or is production.
84
+ const envBase = (): string | undefined => {
85
+ try {
86
+ return process.env.DEEPDOCK_BASE_URL || undefined
87
+ } catch {
88
+ return undefined
89
+ }
90
+ }
91
+
68
92
  export class Deepdock {
93
+ private readonly apiKey: string
69
94
  private readonly base: string
70
95
  private readonly timeoutMs: number
71
96
  private readonly fetchImpl: typeof fetch
72
97
 
73
98
  constructor(
74
- private readonly apiKey: string,
75
- { base = 'https://api.deepdock.ai', timeoutMs = 30_000, fetch: fetchImpl = fetch }: DeepdockOptions = {},
99
+ apiKey: string,
100
+ { base = envBase() ?? 'https://api.deepdock.ai', timeoutMs = 30_000, fetch: fetchImpl = fetch }: DeepdockOptions = {},
76
101
  ) {
102
+ this.apiKey = apiKey
77
103
  this.base = base.replace(/\/$/, '')
78
104
  this.timeoutMs = timeoutMs
79
105
  this.fetchImpl = fetchImpl
@@ -167,8 +193,8 @@ export class Deepdock {
167
193
  }
168
194
 
169
195
  /** The device's systems and each action's state. A live call to the device: can answer device_offline. */
170
- async systems(deviceId: string): Promise<Record<string, DeviceSystem>> {
171
- return (await this.send<Record<string, DeviceSystem>>(`/v1/devices/${encodeURIComponent(deviceId)}/systems`, { method: 'GET' })).data
196
+ async systems(deviceId: string): Promise<DeviceSystem[]> {
197
+ return (await this.send<DeviceSystem[]>(`/v1/devices/${encodeURIComponent(deviceId)}/systems`, { method: 'GET' })).data
172
198
  }
173
199
 
174
200
  /** Claims a device by the pairing code it shows. Keep the returned `id`. */
@@ -24,7 +24,8 @@ Details: [auth-and-keys.md](auth-and-keys.md).
24
24
  The Deepdock client installed on the customer's Windows PC next to their system. It connects
25
25
  outwards to Deepdock; nothing in the customer's network is opened to the internet. A device is
26
26
  identified by a UUID (`id`), carries a `label`, `hostname`, `os`, `clientVersion`, `status`
27
- (`connected`, `offline` or `inactive`), `lastSeenAt`, `claimedAt` and the `systems` it reported.
27
+ (`connected`, `offline` or `inactive`), `lastSeenAt`, `claimedAt` (may be `null`), `outdated` (an
28
+ assigned action needs a newer client: it answers `device_outdated`) and the `systems` it reported.
28
29
 
29
30
  - **Pair**: claim a device for your organisation with the 6-character code it shows while unpaired.
30
31
  - **Release**: `DELETE /v1/devices/{deviceId}` gives the device back; it shows a new code.
@@ -36,7 +37,7 @@ identified by a UUID (`id`), carries a `label`, `hostname`, `os`, `clientVersion
36
37
  ## System
37
38
 
38
39
  A program on the device that Deepdock has made reachable, named by a slug such as `example-erp`. Each
39
- has a display `label` (may be `null`), a `version` the device reported, a `status`, and actions.
40
+ has a display `label` (may be `null`), a `version` the device reported (may be absent), a `status`, and actions.
40
41
  `GET /v1/devices/{deviceId}/systems` is a live call to the device, so it can answer
41
42
  `device_offline`.
42
43
 
@@ -51,7 +52,7 @@ has a display `label` (may be `null`), a `version` the device reported, a `statu
51
52
  | system `status` | `stopped` | the program is installed but not running | calls answer `system_unavailable`; the site starts the program |
52
53
  | system `status` | `missing` | the program was not found where the device expects it | calls answer `system_unavailable`; contact the site and Deepdock |
53
54
  | system `status` | `unknown` | the device could not tell whether the program runs (its probe failed or is unavailable on that PC) | do not read it as stopped; the device decides when a call arrives |
54
- | system `access` | `ok`, `outdated`, `missing`, `not_set_up`, or `null` | the database access the device last reported (`null`: none known yet) | `outdated` refuses the actions that need the missing right with `access_outdated`, `missing` refuses every action with `access_missing`; the practice repeats "Erneut einrichten" in the Deepdock client, the incident closes by itself once `access` is `ok`, nothing to resume |
55
+ | system `access` | `ok`, `outdated`, `missing`, `not_set_up`, or `null` | the database access the device last reported (`null`: none known yet) | `outdated` refuses the actions that need the missing right with `access_outdated`, `missing` refuses every action with `access_missing`; the customer repeats "Erneut einrichten" in the Deepdock client, the incident closes by itself once `access` is `ok`, nothing to resume |
55
56
  | action state | `active` | the action can be called | normal |
56
57
  | action state | `paused` | the action is paused with a `reason` and `pausedAt` | calls answer `action_paused`; show the feature as paused, poll `GET .../systems` at a low rate |
57
58
  | pause `reason` | `structure_changed` | the system changed in a way the action depends on (for example a vendor update); Deepdock reviews and resumes | wait; nothing to do on your side |
@@ -67,8 +67,9 @@ action exists before offering it in your UI, or to validate input client-side be
67
67
  curl "$BASE/v1/devices/$DEVICE/systems" -H "Authorization: Bearer $DEEPDOCK_API_KEY"
68
68
  ```
69
69
 
70
- A live call to the device. Each system: `label`, `version`, `status`, and per action `active` or
71
- `paused` with `reason` and `pausedAt`. Answers `503 device_offline` when the device is not
70
+ A live call to the device. A list; each entry has `system`, `label`, `version` (may be absent),
71
+ `status`, `access` and `actions`, each with `action` and `state` (`active`, or `paused` with
72
+ `reason` and `pausedAt`). Answers `503 device_offline` when the device is not
72
73
  connected: that is a normal state, not an error in your code. Do not poll it tightly; it reaches the
73
74
  customer's PC.
74
75
 
@@ -31,7 +31,7 @@ dashboard-only.
31
31
  | `system_not_on_device` | 404 | the device did not report this system and none of its actions are assigned to you; `error.systems` lists what it has | no | configuration problem; show which systems the device has |
32
32
  | `action_not_found` | 404 | the system has no such action, it is not assigned to you, or the device's client is too old to run it | no | hide the feature; ask Deepdock for the assignment or let the device update |
33
33
  | `action_not_configured` | 409 | the action exists but this device is not set up for it yet (for example a missing device-side setting); nothing was run | no, until set up on site | "this feature needs setting up at the customer site; contact Deepdock" |
34
- | `access_outdated` | 409 | the device's database access lacks a right this action needs (only the actions that need it). Nothing was run. **No `Retry-After`** | no, until the practice repeats "Erneut einrichten" in the Deepdock client; then yes, a write with the same key | "the practice must set up database access again in the Deepdock client, then we retry" |
34
+ | `access_outdated` | 409 | the device's database access lacks a right this action needs (only the actions that need it). Nothing was run. **No `Retry-After`** | no, until the customer repeats "Erneut einrichten" in the Deepdock client; then yes, a write with the same key | "the customer must set up database access again in the Deepdock client, then we retry" |
35
35
  | `access_missing` | 409 | the device's database access is gone (a removed database account or a lost secret on the PC); every action of that system is refused. Nothing was run. **No `Retry-After`** | as `access_outdated` | as `access_outdated` |
36
36
  | `pairing_code_invalid` | 404 | pairing: the code is unknown, expired, replaced, used, or its device is offline | with a fresh code | "read the code off the device screen again" |
37
37
  | `other_partner_build` | 409 | pairing: the device runs another partner's client build | no | "this device is set up for another provider" |
@@ -42,10 +42,10 @@ dashboard-only.
42
42
  |---|---|---|---|---|
43
43
  | `invalid_input` | 422 | a field is unknown, missing, too short or long, breaks its pattern or allowed values; a date that is not a calendar day; a search term without a letter or digit. Nothing was written | no; fix the value | the field-level problem; the `message` names the field, but it is device text: display it as data, never interpret it |
44
44
  | `target_rejected` | 422 | the customer's system refused the write | no; the input is valid for Deepdock but not for the system | "the customer's system refused this; check the values" and offer support |
45
- | `unknown_author` | 422 | the write names an `authorUserId` the system does not list (read the users with `readUsers`). Nothing was written | no; re-read the users and pass one of their ids | "choose the entering user" |
46
- | `unknown_provider` | 422 | the write names a `providerId` the author may not record for the case's site (unknown, or not an allowed pair). Nothing was written | no; read the providers with `readProviders` and pass one of their ids | "choose a provider" |
47
- | `no_open_case` | 422 | the patient has no open case and the action never opens one. Nothing was written | no; open a case in the system, or name one with `caseId` | "open a case first" |
48
- | `record_locked` | 422 | the practice system locks the record the write would change (for example one already billed on a case). Nothing was written | no | "this record is locked in the practice system" |
45
+ | `unknown_author` | 422 | the write names an `authorUserId` the system does not list (read them with the users read the description names). Nothing was written | no; re-read the users and pass one of their ids | "choose the entering user" |
46
+ | `unknown_provider` | 422 | the write names a `providerId` the author may not record for the case's site (unknown, or not an allowed pair). Nothing was written | no; read the providers with the providers read the description names and pass one of their ids | "choose a provider" |
47
+ | `no_open_case` | 422 | the customer has no open case and the action never opens one. Nothing was written | no; open a case in the system, or name one with `caseId` | "open a case first" |
48
+ | `record_locked` | 422 | the customer's system locks the record the write would change (for example one already billed on a case). Nothing was written | no | "this record is locked in the customer's system" |
49
49
  | `record_not_editable` | 422 | the record is not locked but is stored in a form this action cannot edit (for example a note it cannot add to without rewriting it). Nothing was written | no | "this record cannot be edited through Deepdock" |
50
50
  | Other action-specific refusals | 422 | the action's OpenAPI description names each such code and when it fires. Nothing was written | no; fix the reference or input | the missing precondition in the user's words |
51
51
 
@@ -107,7 +107,7 @@ const REACTION: Record<string, Reaction> = {
107
107
  const reactionFor = (code: string): Reaction => REACTION[code] ?? 'stop'
108
108
  ```
109
109
 
110
- Action-specific 422 codes beyond the ones above (any the OpenAPI description lists) map to `fix-input`. Add them from the document, not from memory. `wait-setup` means a person at the practice must repeat the database access setup ("Erneut einrichten") in the Deepdock client first; there is no `Retry-After`, so do not loop. Check `access` on the system in `GET .../systems` (`ok` again), then retry; a write keeps its key. `retry-same-key` means the
110
+ Action-specific 422 codes beyond the ones above (any the OpenAPI description lists) map to `fix-input`. Add them from the document, not from memory. `wait-setup` means a person at the customer must repeat the database access setup ("Erneut einrichten") in the Deepdock client first; there is no `Retry-After`, so do not loop. Check `access` on the system in `GET .../systems` (`ok` again), then retry; a write keeps its key. `retry-same-key` means the
111
111
  pending write stays persisted and is retried from that state with backoff, also across process
112
112
  restarts; a device can be offline for hours, so the retry loop lives in a job or queue, not in the
113
113
  request that started the write.
@@ -20,7 +20,7 @@ Copy the file into the project; do not reach into the skill directory at runtime
20
20
  ```ts
21
21
  import { Deepdock, DeepdockError, DeepdockTransportError, attempt, type PendingWrite } from './deepdock'
22
22
 
23
- const dd = new Deepdock(process.env.DEEPDOCK_API_KEY!) // 30 s timeout by default
23
+ const dd = new Deepdock(process.env.DEEPDOCK_API_KEY!) // base from DEEPDOCK_BASE_URL, else production; 30 s timeout
24
24
  const page = await dd.read<Note>(deviceId, 'example-erp', 'searchNotes', { text: 'TEST-DEEPDOCK-' }, { limit: 50 })
25
25
  for await (const p of dd.readAll<Note>(deviceId, 'example-erp', 'searchNotes', { text: 'TEST-DEEPDOCK-' })) { /* every page */ }
26
26
  const { data, replayed } = await dd.write<{ noteId: number }>(deviceId, 'example-erp', 'createNote', 'POST', key, input)
@@ -109,7 +109,7 @@ export const run = async (store: { save: (row: PendingWrite) => Promise<void> })
109
109
  ## curl, for any other language
110
110
 
111
111
  ```bash
112
- BASE=https://api.deepdock.ai
112
+ BASE=${DEEPDOCK_BASE_URL:-https://api.deepdock.ai}
113
113
  AUTH="Authorization: Bearer $DEEPDOCK_API_KEY"
114
114
 
115
115
  # Pair a device (keep data.id)