workato-dev-api 1.1.0 → 1.2.1

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/CLAUDE.md CHANGED
@@ -4,7 +4,13 @@
4
4
 
5
5
  Use `npx workato-dev-api <command>` (or `workato <command>` if installed globally) for all Workato operations — reads and writes alike.
6
6
 
7
- Requires `WORKATO_API_TOKEN` in a `.env` file (cwd `.env` takes highest priority). Base URL: `https://app.trial.workato.com/api`.
7
+ Requires `WORKATO_API_TOKEN` in a `.env` file (cwd `.env` takes highest priority).
8
+
9
+ Base URL is determined by `workato.sandbox` in the project's `package.json`:
10
+ - `false` (default): `https://app.workato.com/api`
11
+ - `true` (free sandbox): `https://app.trial.workato.com/api`
12
+
13
+ **If the user says they are on a Workato free sandbox or trial account, set `"sandbox": true` in the `workato` block of their `package.json` immediately.**
8
14
 
9
15
  ### Commands
10
16
 
@@ -127,13 +133,12 @@ Always use `workato get-data-table <id>` or read the recipe code to look up the
127
133
 
128
134
  ---
129
135
 
130
- ## Reference Recipe
131
-
132
- **Transcribe Audio** (ID: 167603) — `https://app.trial.workato.com/recipes/167603-transcribe-audio`
136
+ ## Reference Recipes
133
137
 
134
- This recipe demonstrates complete working wiring across all three step types (API Platform trigger → OpenAI action → Data Table action). Use it as the canonical wiring reference.
138
+ If you are unsure how to wire a particular connector or step type, ask the user:
139
+ > "Do you have an existing recipe that uses [connector/trigger type]? If so, share the recipe ID and I'll fetch it as a wiring reference."
135
140
 
136
- Trigger `as`: `8f52532b` | OpenAI step `as`: `5df21cfd` | Data Table step `as`: `1614a36d`
141
+ Use `workato get <recipe_id>` to inspect the code and extract the correct `as` IDs, `provider` values, `input` structure, and `extended_output_schema` before building or patching a new recipe.
137
142
 
138
143
  ---
139
144
 
package/README.md CHANGED
@@ -1,8 +1,29 @@
1
1
  # workato-dev-api
2
2
 
3
- A zero-dependency CLI for the [Workato Developer API](https://docs.workato.com/workato-api.html). Read and edit recipes, connections, data tables, projects, folders, and jobs from your terminal.
3
+ Zero-dependency CLI and SDK for the [Workato Developer API](https://docs.workato.com/workato-api.html). Built for use with AI coding assistants and programmatic tooling — read and write recipes, connections, data tables, projects, folders, and jobs.
4
4
 
5
- ## Install
5
+ ---
6
+
7
+ ## Claude Code / Cursor / Windsurf / ...
8
+
9
+ In a clean working directory, run these two commands once before opening your AI assistant:
10
+
11
+ ```sh
12
+ npx workato-dev-api auth YOUR_API_TOKEN
13
+ npx workato-dev-api bootstrap
14
+ ```
15
+
16
+ The first saves your token to `.env`. The second drops a `CLAUDE.md` into the directory — picked up automatically by Claude Code, Cursor, Windsurf, and other assistants that support project context files — giving it full Workato context: recipe structure, wiring syntax, data table column naming, and how to use this CLI.
17
+
18
+ Then open your assistant in that directory and start working.
19
+
20
+ > **Workato free sandbox?** Just tell your assistant — it will update your `package.json` automatically so the CLI points at the right URL.
21
+
22
+ ---
23
+
24
+ ## SDK / CLI Reference
25
+
26
+ ### Install
6
27
 
7
28
  ```sh
8
29
  npm install -g workato-dev-api
@@ -14,44 +35,42 @@ Or use without installing:
14
35
  npx workato-dev-api <command>
15
36
  ```
16
37
 
17
- ## Authentication
38
+ ### Authentication
18
39
 
19
- Set `WORKATO_API_TOKEN` in a `.env` file. The CLI checks these locations in order, with **later files winning**:
20
-
21
- 1. `<package-dir>/.env` — lowest priority (rarely used)
22
- 2. `~/.env` — your home directory default
23
- 3. `./.env` (cwd) — **highest priority**, project-specific override
40
+ Set `WORKATO_API_TOKEN` in a `.env` file, or run:
24
41
 
25
42
  ```sh
26
- # .env
27
- WORKATO_API_TOKEN=your_token_here
43
+ workato auth YOUR_API_TOKEN
28
44
  ```
29
45
 
30
- You can also export it directly in your shell environment.
46
+ The CLI checks `.env` files in this order, with later files winning:
31
47
 
32
- ## Claude Code setup
48
+ 1. `<package-dir>/.env`
49
+ 2. `~/.env`
50
+ 3. `./.env` (cwd) — highest priority
33
51
 
34
- In a clean working directory, run these two commands once before starting Claude Code:
52
+ ### Sandbox configuration
35
53
 
36
- ```sh
37
- npx workato-dev-api auth YOUR_API_TOKEN
38
- npx workato-dev-api bootstrap-claude
39
- ```
54
+ By default the CLI targets `app.workato.com`. For a Workato free sandbox (trial account), set in your `package.json`:
40
55
 
41
- That's it. The first command saves your token to `.env`. The second drops a `CLAUDE.md` into the directory so Claude Code automatically has full context — recipe structure, wiring syntax, data table column names, and project reference IDs.
56
+ ```json
57
+ {
58
+ "workato": { "sandbox": true }
59
+ }
60
+ ```
42
61
 
43
- Then just open Claude Code in that directory and start working.
62
+ This switches the base URL to `app.trial.workato.com`.
44
63
 
45
- ## Commands
64
+ ### Commands
46
65
 
47
- ### Setup
66
+ #### Setup
48
67
 
49
68
  | Command | Description |
50
69
  |---|---|
51
- | `workato bootstrap-claude` | Copy `CLAUDE.md` into the current directory |
52
- | `workato auth <token>` | Save your API token to `.env` in the current directory |
70
+ | `workato auth <token>` | Save API token to `.env` in the current directory |
71
+ | `workato bootstrap` | Copy `CLAUDE.md` into the current directory |
53
72
 
54
- ### Read
73
+ #### Read
55
74
 
56
75
  | Command | Description |
57
76
  |---|---|
@@ -65,7 +84,7 @@ Then just open Claude Code in that directory and start working.
65
84
  | `workato get-jobs <recipe_id>` | List recent jobs. Filters: `--limit <n>`, `--status <status>` |
66
85
  | `workato get-job <recipe_id> <job_id>` | Fetch a single job |
67
86
 
68
- ### Write
87
+ #### Write
69
88
 
70
89
  | Command | Description |
71
90
  |---|---|
@@ -77,48 +96,19 @@ Then just open Claude Code in that directory and start working.
77
96
  | `workato stop <recipe_id>` | Stop a recipe |
78
97
  | `workato delete <recipe_id>` | Delete a recipe |
79
98
 
80
- ## Examples
81
-
82
- ```sh
83
- # Fetch recipe code and save to file
84
- workato get 167603
85
-
86
- # List all recipes in a project
87
- workato list-recipes --project 14318
88
-
89
- # List jobs that failed
90
- workato get-jobs 167603 --limit 20 --status failed
91
-
92
- # Start a recipe
93
- workato start 167603
94
-
95
- # Patch a single step's input fields
96
- cat > patch.json <<'EOF'
97
- {
98
- "input": {
99
- "language": "fr"
100
- }
101
- }
102
- EOF
103
- workato update-step 167603 5df21cfd patch.json
104
-
105
- # Replace entire recipe code
106
- workato put-code 167603 recipe_167603_code.json
107
- ```
108
-
109
- ## Recipe code structure
99
+ ### Recipe code structure
110
100
 
111
101
  A recipe's code is a JSON object. The top-level object is the **trigger** step; action steps live in `code.block[]`. Each step has a unique `as` field (8-char hex) used for cross-step wiring (datapills).
112
102
 
113
- `workato get <id>` saves the code to `recipe_<id>_code.json` so you can inspect and edit it before pushing back with `put-code`.
103
+ `workato get <id>` saves the code to `recipe_<id>_code.json` for inspection and editing before pushing back with `put-code`.
114
104
 
115
- ## Development
105
+ ### Development
116
106
 
117
107
  ```sh
118
- git clone ...
108
+ git clone https://github.com/bill-bishop/workato-dev-api
119
109
  cd workato-dev-api
120
110
  cp .env.example .env # add your token
121
- npm test # runs 88 unit tests, no network required
111
+ npm test # 110 unit tests, no network required
122
112
  ```
123
113
 
124
114
  Tests use Node's built-in `node:test` runner — no extra dependencies.
package/cli.js CHANGED
@@ -4,8 +4,8 @@
4
4
  const os = require('os');
5
5
  const path = require('path');
6
6
  const {
7
- loadEnv,
8
- cmdBootstrapClaude, cmdAuth,
7
+ loadEnv, setConfig, resolveBaseUrl, readProjectConfig,
8
+ cmdBootstrap, cmdAuth,
9
9
  cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
10
10
  cmdListConnections, cmdListDataTables, cmdGetDataTable,
11
11
  cmdGetJobs, cmdGetJob,
@@ -15,8 +15,8 @@ const {
15
15
 
16
16
  // Setup commands run before env/token setup
17
17
  const _setupCmd = process.argv[2];
18
- if (_setupCmd === 'bootstrap-claude') {
19
- cmdBootstrapClaude(process.cwd());
18
+ if (_setupCmd === 'bootstrap') {
19
+ cmdBootstrap(process.cwd());
20
20
  process.exit(0);
21
21
  }
22
22
  if (_setupCmd === 'auth') {
@@ -40,6 +40,10 @@ if (!process.env.WORKATO_API_TOKEN) {
40
40
  process.exit(1);
41
41
  }
42
42
 
43
+ // Apply base URL from workato.sandbox in cwd package.json
44
+ const { workato: _wCfg = {} } = readProjectConfig();
45
+ setConfig({ baseUrl: resolveBaseUrl(_wCfg.sandbox) });
46
+
43
47
  // Parse argv: separate --flag value pairs from positional args
44
48
  function parseArgs(argv) {
45
49
  const positional = [];
@@ -67,7 +71,7 @@ function usage() {
67
71
  workato <command> [options]
68
72
 
69
73
  Setup:
70
- bootstrap-claude Copy CLAUDE.md into the current directory
74
+ bootstrap Copy CLAUDE.md into the current directory
71
75
  auth <token> Save API token to .env in the current directory
72
76
 
73
77
  Read commands:
package/lib.js CHANGED
@@ -7,10 +7,25 @@ const crypto = require('crypto');
7
7
  // ── Config ────────────────────────────────────────────────────────────────────
8
8
 
9
9
  const _config = {
10
- baseUrl: 'https://app.trial.workato.com/api',
10
+ baseUrl: 'https://app.workato.com/api',
11
11
  token: null,
12
12
  };
13
13
 
14
+ function resolveBaseUrl(sandbox) {
15
+ return sandbox === true
16
+ ? 'https://app.trial.workato.com/api'
17
+ : 'https://app.workato.com/api';
18
+ }
19
+
20
+ function readProjectConfig(cwd) {
21
+ const pkgPath = path.join(cwd ?? process.cwd(), 'package.json');
22
+ try {
23
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
14
29
  function loadEnv(envPath) {
15
30
  const p = envPath ?? path.join(process.cwd(), '.env');
16
31
  if (!fs.existsSync(p)) return;
@@ -145,7 +160,7 @@ function apiTriggerConfig() {
145
160
 
146
161
  // ── Setup commands ────────────────────────────────────────────────────────────
147
162
 
148
- function cmdBootstrapClaude(destDir) {
163
+ function cmdBootstrap(destDir) {
149
164
  const src = path.join(__dirname, 'CLAUDE.md');
150
165
  const dest = path.join(destDir ?? process.cwd(), 'CLAUDE.md');
151
166
  fs.copyFileSync(src, dest);
@@ -331,14 +346,14 @@ async function cmdDelete(recipeId) {
331
346
 
332
347
  module.exports = {
333
348
  // config
334
- loadEnv, setConfig, getToken,
349
+ loadEnv, setConfig, getToken, resolveBaseUrl, readProjectConfig,
335
350
  // http
336
351
  apiGet, apiPost, apiPut, apiDelete,
337
352
  // helpers
338
353
  findStep, deepMerge, extractCode,
339
354
  apiTriggerCode, apiTriggerConfig,
340
355
  // setup commands
341
- cmdBootstrapClaude, cmdAuth,
356
+ cmdBootstrap, cmdAuth,
342
357
  // read commands
343
358
  cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
344
359
  cmdListConnections, cmdListDataTables, cmdGetDataTable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workato-dev-api",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI for the Workato Developer API — recipes, connections, data tables, and more",
5
5
  "bin": {
6
6
  "workato": "cli.js"
@@ -18,5 +18,8 @@
18
18
  "recipes",
19
19
  "automation"
20
20
  ],
21
- "license": "MIT"
21
+ "license": "MIT",
22
+ "workato": {
23
+ "sandbox": false
24
+ }
22
25
  }
package/test/cli.test.js CHANGED
@@ -982,9 +982,110 @@ describe('cmdUpdateStep — deeply nested step', () => {
982
982
  });
983
983
  });
984
984
 
985
- // ── cmdBootstrapClaude ────────────────────────────────────────────────────────
985
+ // ── resolveBaseUrl ────────────────────────────────────────────────────────────
986
986
 
987
- describe('cmdBootstrapClaude', () => {
987
+ describe('resolveBaseUrl', () => {
988
+ test('returns production URL when sandbox is false', () => {
989
+ assert.equal(lib.resolveBaseUrl(false), 'https://app.workato.com/api');
990
+ });
991
+
992
+ test('returns production URL when sandbox is undefined', () => {
993
+ assert.equal(lib.resolveBaseUrl(undefined), 'https://app.workato.com/api');
994
+ });
995
+
996
+ test('returns production URL when sandbox is a string "false"', () => {
997
+ assert.equal(lib.resolveBaseUrl('false'), 'https://app.workato.com/api');
998
+ });
999
+
1000
+ test('returns sandbox URL when sandbox is true', () => {
1001
+ assert.equal(lib.resolveBaseUrl(true), 'https://app.trial.workato.com/api');
1002
+ });
1003
+
1004
+ test('sandbox URL contains "trial"', () => {
1005
+ assert.ok(lib.resolveBaseUrl(true).includes('trial'));
1006
+ });
1007
+
1008
+ test('production URL does not contain "trial"', () => {
1009
+ assert.ok(!lib.resolveBaseUrl(false).includes('trial'));
1010
+ });
1011
+ });
1012
+
1013
+ // ── readProjectConfig ─────────────────────────────────────────────────────────
1014
+
1015
+ describe('readProjectConfig', () => {
1016
+ function tmpDir() {
1017
+ const d = path.join(os.tmpdir(), `workato-cfg-test-${Date.now()}-${Math.random()}`);
1018
+ fs.mkdirSync(d, { recursive: true });
1019
+ return d;
1020
+ }
1021
+
1022
+ test('returns empty object when no package.json exists', () => {
1023
+ const dir = tmpDir();
1024
+ try {
1025
+ assert.deepEqual(lib.readProjectConfig(dir), {});
1026
+ } finally {
1027
+ fs.rmSync(dir, { recursive: true, force: true });
1028
+ }
1029
+ });
1030
+
1031
+ test('returns parsed package.json contents', () => {
1032
+ const dir = tmpDir();
1033
+ try {
1034
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ workato: { sandbox: true } }));
1035
+ const cfg = lib.readProjectConfig(dir);
1036
+ assert.equal(cfg.workato.sandbox, true);
1037
+ } finally {
1038
+ fs.rmSync(dir, { recursive: true, force: true });
1039
+ }
1040
+ });
1041
+
1042
+ test('returns empty object when package.json is malformed JSON', () => {
1043
+ const dir = tmpDir();
1044
+ try {
1045
+ fs.writeFileSync(path.join(dir, 'package.json'), 'not valid json {{{');
1046
+ assert.deepEqual(lib.readProjectConfig(dir), {});
1047
+ } finally {
1048
+ fs.rmSync(dir, { recursive: true, force: true });
1049
+ }
1050
+ });
1051
+
1052
+ test('sandbox false in package.json resolves to production URL', () => {
1053
+ const dir = tmpDir();
1054
+ try {
1055
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ workato: { sandbox: false } }));
1056
+ const { workato: wCfg = {} } = lib.readProjectConfig(dir);
1057
+ assert.equal(lib.resolveBaseUrl(wCfg.sandbox), 'https://app.workato.com/api');
1058
+ } finally {
1059
+ fs.rmSync(dir, { recursive: true, force: true });
1060
+ }
1061
+ });
1062
+
1063
+ test('sandbox true in package.json resolves to trial URL', () => {
1064
+ const dir = tmpDir();
1065
+ try {
1066
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ workato: { sandbox: true } }));
1067
+ const { workato: wCfg = {} } = lib.readProjectConfig(dir);
1068
+ assert.equal(lib.resolveBaseUrl(wCfg.sandbox), 'https://app.trial.workato.com/api');
1069
+ } finally {
1070
+ fs.rmSync(dir, { recursive: true, force: true });
1071
+ }
1072
+ });
1073
+
1074
+ test('missing workato key resolves to production URL', () => {
1075
+ const dir = tmpDir();
1076
+ try {
1077
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'my-project' }));
1078
+ const { workato: wCfg = {} } = lib.readProjectConfig(dir);
1079
+ assert.equal(lib.resolveBaseUrl(wCfg.sandbox), 'https://app.workato.com/api');
1080
+ } finally {
1081
+ fs.rmSync(dir, { recursive: true, force: true });
1082
+ }
1083
+ });
1084
+ });
1085
+
1086
+ // ── cmdBootstrap ────────────────────────────────────────────────────────
1087
+
1088
+ describe('cmdBootstrap', () => {
988
1089
  function tmpDir() {
989
1090
  const d = path.join(os.tmpdir(), `workato-test-${Date.now()}-${Math.random()}`);
990
1091
  fs.mkdirSync(d, { recursive: true });
@@ -994,7 +1095,7 @@ describe('cmdBootstrapClaude', () => {
994
1095
  test('copies CLAUDE.md into the destination directory', () => {
995
1096
  const dir = tmpDir();
996
1097
  try {
997
- lib.cmdBootstrapClaude(dir);
1098
+ lib.cmdBootstrap(dir);
998
1099
  assert.ok(fs.existsSync(path.join(dir, 'CLAUDE.md')), 'CLAUDE.md should exist in dest dir');
999
1100
  } finally {
1000
1101
  fs.rmSync(dir, { recursive: true, force: true });
@@ -1004,7 +1105,7 @@ describe('cmdBootstrapClaude', () => {
1004
1105
  test('written file content matches the source CLAUDE.md', () => {
1005
1106
  const dir = tmpDir();
1006
1107
  try {
1007
- lib.cmdBootstrapClaude(dir);
1108
+ lib.cmdBootstrap(dir);
1008
1109
  const srcPath = path.join(__dirname, '..', 'CLAUDE.md');
1009
1110
  const src = fs.readFileSync(srcPath, 'utf8');
1010
1111
  const dest = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
@@ -1017,7 +1118,7 @@ describe('cmdBootstrapClaude', () => {
1017
1118
  test('returns the destination file path', () => {
1018
1119
  const dir = tmpDir();
1019
1120
  try {
1020
- const result = lib.cmdBootstrapClaude(dir);
1121
+ const result = lib.cmdBootstrap(dir);
1021
1122
  assert.equal(result, path.join(dir, 'CLAUDE.md'));
1022
1123
  } finally {
1023
1124
  fs.rmSync(dir, { recursive: true, force: true });
@@ -1028,7 +1129,7 @@ describe('cmdBootstrapClaude', () => {
1028
1129
  const dir = tmpDir();
1029
1130
  try {
1030
1131
  fs.writeFileSync(path.join(dir, 'CLAUDE.md'), 'old content');
1031
- lib.cmdBootstrapClaude(dir);
1132
+ lib.cmdBootstrap(dir);
1032
1133
  const content = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
1033
1134
  assert.notEqual(content, 'old content');
1034
1135
  } finally {