workato-dev-api 1.0.1 → 1.2.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.
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
@@ -31,13 +31,16 @@ You can also export it directly in your shell environment.
31
31
 
32
32
  ## Claude Code setup
33
33
 
34
- In a clean working directory, run this once before starting Claude Code:
34
+ In a clean working directory, run these two commands once before starting Claude Code:
35
35
 
36
36
  ```sh
37
+ npx workato-dev-api auth YOUR_API_TOKEN
37
38
  npx workato-dev-api bootstrap-claude
38
39
  ```
39
40
 
40
- This writes a `CLAUDE.md` with full context — recipe code structure, wiring syntax, data table column naming, and project reference IDs — directly into your working directory where Claude Code will pick it up automatically.
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.
42
+
43
+ Then open Claude Code in that directory and start working. If you're on a **Workato free sandbox**, just tell Claude — it will update your `package.json` automatically so the CLI points at the right URL.
41
44
 
42
45
  ## Commands
43
46
 
@@ -46,6 +49,7 @@ This writes a `CLAUDE.md` with full context — recipe code structure, wiring sy
46
49
  | Command | Description |
47
50
  |---|---|
48
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 |
49
53
 
50
54
  ### Read
51
55
 
package/cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const fs = require('fs');
5
4
  const os = require('os');
6
5
  const path = require('path');
7
6
  const {
8
- loadEnv,
7
+ loadEnv, setConfig, resolveBaseUrl, readProjectConfig,
8
+ cmdBootstrapClaude, cmdAuth,
9
9
  cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
10
10
  cmdListConnections, cmdListDataTables, cmdGetDataTable,
11
11
  cmdGetJobs, cmdGetJob,
@@ -13,12 +13,19 @@ const {
13
13
  cmdStart, cmdStop, cmdDelete,
14
14
  } = require('./lib');
15
15
 
16
- // bootstrap-claude runs before env/token setup — handle it immediately
17
- if (process.argv[2] === 'bootstrap-claude') {
18
- const src = path.join(__dirname, 'CLAUDE.md');
19
- const dest = path.join(process.cwd(), 'CLAUDE.md');
20
- fs.copyFileSync(src, dest);
21
- console.log(`CLAUDE.md written to ${dest}`);
16
+ // Setup commands run before env/token setup
17
+ const _setupCmd = process.argv[2];
18
+ if (_setupCmd === 'bootstrap-claude') {
19
+ cmdBootstrapClaude(process.cwd());
20
+ process.exit(0);
21
+ }
22
+ if (_setupCmd === 'auth') {
23
+ const token = process.argv[3];
24
+ if (!token) {
25
+ console.error('Usage: workato auth <token>');
26
+ process.exit(1);
27
+ }
28
+ cmdAuth(token, process.cwd());
22
29
  process.exit(0);
23
30
  }
24
31
 
@@ -29,10 +36,14 @@ loadEnv(path.join(os.homedir(), '.env'));
29
36
  loadEnv(path.join(process.cwd(), '.env'));
30
37
 
31
38
  if (!process.env.WORKATO_API_TOKEN) {
32
- console.error('Error: WORKATO_API_TOKEN not set.\nCreate a .env file in your current directory with:\n WORKATO_API_TOKEN=your_token_here');
39
+ console.error('Error: WORKATO_API_TOKEN not set.\nRun: workato auth <your_token>\nOr create a .env file with: WORKATO_API_TOKEN=your_token_here');
33
40
  process.exit(1);
34
41
  }
35
42
 
43
+ // Apply base URL from workato.sandbox in cwd package.json
44
+ const { workato: _wCfg = {} } = readProjectConfig();
45
+ setConfig({ baseUrl: resolveBaseUrl(_wCfg.sandbox) });
46
+
36
47
  // Parse argv: separate --flag value pairs from positional args
37
48
  function parseArgs(argv) {
38
49
  const positional = [];
@@ -61,6 +72,7 @@ workato <command> [options]
61
72
 
62
73
  Setup:
63
74
  bootstrap-claude Copy CLAUDE.md into the current directory
75
+ auth <token> Save API token to .env in the current directory
64
76
 
65
77
  Read commands:
66
78
  get <recipe_id> Fetch recipe code → recipe_<id>_code.json
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;
@@ -143,6 +158,30 @@ function apiTriggerConfig() {
143
158
  ];
144
159
  }
145
160
 
161
+ // ── Setup commands ────────────────────────────────────────────────────────────
162
+
163
+ function cmdBootstrapClaude(destDir) {
164
+ const src = path.join(__dirname, 'CLAUDE.md');
165
+ const dest = path.join(destDir ?? process.cwd(), 'CLAUDE.md');
166
+ fs.copyFileSync(src, dest);
167
+ console.log(`CLAUDE.md written to ${dest}`);
168
+ return dest;
169
+ }
170
+
171
+ function cmdAuth(token, destDir) {
172
+ const envPath = path.join(destDir ?? process.cwd(), '.env');
173
+ let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
174
+ const line = `WORKATO_API_TOKEN=${token}`;
175
+ if (/^WORKATO_API_TOKEN=/m.test(content)) {
176
+ content = content.replace(/^WORKATO_API_TOKEN=.*/m, line);
177
+ } else {
178
+ content = content ? content.trimEnd() + '\n' + line + '\n' : line + '\n';
179
+ }
180
+ fs.writeFileSync(envPath, content);
181
+ console.log(`Token saved to ${envPath}`);
182
+ return envPath;
183
+ }
184
+
146
185
  // ── Read commands ─────────────────────────────────────────────────────────────
147
186
 
148
187
  async function cmdGet(recipeId) {
@@ -307,12 +346,14 @@ async function cmdDelete(recipeId) {
307
346
 
308
347
  module.exports = {
309
348
  // config
310
- loadEnv, setConfig, getToken,
349
+ loadEnv, setConfig, getToken, resolveBaseUrl, readProjectConfig,
311
350
  // http
312
351
  apiGet, apiPost, apiPut, apiDelete,
313
352
  // helpers
314
353
  findStep, deepMerge, extractCode,
315
354
  apiTriggerCode, apiTriggerConfig,
355
+ // setup commands
356
+ cmdBootstrapClaude, cmdAuth,
316
357
  // read commands
317
358
  cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
318
359
  cmdListConnections, cmdListDataTables, cmdGetDataTable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workato-dev-api",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
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,6 +982,245 @@ describe('cmdUpdateStep — deeply nested step', () => {
982
982
  });
983
983
  });
984
984
 
985
+ // ── resolveBaseUrl ────────────────────────────────────────────────────────────
986
+
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
+ // ── cmdBootstrapClaude ────────────────────────────────────────────────────────
1087
+
1088
+ describe('cmdBootstrapClaude', () => {
1089
+ function tmpDir() {
1090
+ const d = path.join(os.tmpdir(), `workato-test-${Date.now()}-${Math.random()}`);
1091
+ fs.mkdirSync(d, { recursive: true });
1092
+ return d;
1093
+ }
1094
+
1095
+ test('copies CLAUDE.md into the destination directory', () => {
1096
+ const dir = tmpDir();
1097
+ try {
1098
+ lib.cmdBootstrapClaude(dir);
1099
+ assert.ok(fs.existsSync(path.join(dir, 'CLAUDE.md')), 'CLAUDE.md should exist in dest dir');
1100
+ } finally {
1101
+ fs.rmSync(dir, { recursive: true, force: true });
1102
+ }
1103
+ });
1104
+
1105
+ test('written file content matches the source CLAUDE.md', () => {
1106
+ const dir = tmpDir();
1107
+ try {
1108
+ lib.cmdBootstrapClaude(dir);
1109
+ const srcPath = path.join(__dirname, '..', 'CLAUDE.md');
1110
+ const src = fs.readFileSync(srcPath, 'utf8');
1111
+ const dest = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
1112
+ assert.equal(dest, src);
1113
+ } finally {
1114
+ fs.rmSync(dir, { recursive: true, force: true });
1115
+ }
1116
+ });
1117
+
1118
+ test('returns the destination file path', () => {
1119
+ const dir = tmpDir();
1120
+ try {
1121
+ const result = lib.cmdBootstrapClaude(dir);
1122
+ assert.equal(result, path.join(dir, 'CLAUDE.md'));
1123
+ } finally {
1124
+ fs.rmSync(dir, { recursive: true, force: true });
1125
+ }
1126
+ });
1127
+
1128
+ test('overwrites an existing CLAUDE.md', () => {
1129
+ const dir = tmpDir();
1130
+ try {
1131
+ fs.writeFileSync(path.join(dir, 'CLAUDE.md'), 'old content');
1132
+ lib.cmdBootstrapClaude(dir);
1133
+ const content = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
1134
+ assert.notEqual(content, 'old content');
1135
+ } finally {
1136
+ fs.rmSync(dir, { recursive: true, force: true });
1137
+ }
1138
+ });
1139
+ });
1140
+
1141
+ // ── cmdAuth ───────────────────────────────────────────────────────────────────
1142
+
1143
+ describe('cmdAuth', () => {
1144
+ function tmpDir() {
1145
+ const d = path.join(os.tmpdir(), `workato-auth-test-${Date.now()}-${Math.random()}`);
1146
+ fs.mkdirSync(d, { recursive: true });
1147
+ return d;
1148
+ }
1149
+
1150
+ test('creates .env with token when file does not exist', () => {
1151
+ const dir = tmpDir();
1152
+ try {
1153
+ lib.cmdAuth('mytoken123', dir);
1154
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1155
+ assert.ok(content.includes('WORKATO_API_TOKEN=mytoken123'));
1156
+ } finally {
1157
+ fs.rmSync(dir, { recursive: true, force: true });
1158
+ }
1159
+ });
1160
+
1161
+ test('returns the .env file path', () => {
1162
+ const dir = tmpDir();
1163
+ try {
1164
+ const result = lib.cmdAuth('tok', dir);
1165
+ assert.equal(result, path.join(dir, '.env'));
1166
+ } finally {
1167
+ fs.rmSync(dir, { recursive: true, force: true });
1168
+ }
1169
+ });
1170
+
1171
+ test('updates existing WORKATO_API_TOKEN line in .env', () => {
1172
+ const dir = tmpDir();
1173
+ try {
1174
+ fs.writeFileSync(path.join(dir, '.env'), 'WORKATO_API_TOKEN=oldtoken\n');
1175
+ lib.cmdAuth('newtoken', dir);
1176
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1177
+ assert.ok(content.includes('WORKATO_API_TOKEN=newtoken'), 'should have new token');
1178
+ assert.ok(!content.includes('oldtoken'), 'should not have old token');
1179
+ assert.equal((content.match(/WORKATO_API_TOKEN=/g) || []).length, 1, 'only one token line');
1180
+ } finally {
1181
+ fs.rmSync(dir, { recursive: true, force: true });
1182
+ }
1183
+ });
1184
+
1185
+ test('appends token to .env that has other keys but no WORKATO_API_TOKEN', () => {
1186
+ const dir = tmpDir();
1187
+ try {
1188
+ fs.writeFileSync(path.join(dir, '.env'), 'OTHER_KEY=value\n');
1189
+ lib.cmdAuth('mytoken', dir);
1190
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1191
+ assert.ok(content.includes('OTHER_KEY=value'), 'existing key preserved');
1192
+ assert.ok(content.includes('WORKATO_API_TOKEN=mytoken'), 'token appended');
1193
+ } finally {
1194
+ fs.rmSync(dir, { recursive: true, force: true });
1195
+ }
1196
+ });
1197
+
1198
+ test('token value containing = is preserved verbatim', () => {
1199
+ const dir = tmpDir();
1200
+ try {
1201
+ lib.cmdAuth('tok==extra==', dir);
1202
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1203
+ assert.ok(content.includes('WORKATO_API_TOKEN=tok==extra=='));
1204
+ } finally {
1205
+ fs.rmSync(dir, { recursive: true, force: true });
1206
+ }
1207
+ });
1208
+
1209
+ test('preserves other keys in .env when updating token', () => {
1210
+ const dir = tmpDir();
1211
+ try {
1212
+ fs.writeFileSync(path.join(dir, '.env'), 'FOO=bar\nWORKATO_API_TOKEN=old\nBAZ=qux\n');
1213
+ lib.cmdAuth('updated', dir);
1214
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1215
+ assert.ok(content.includes('FOO=bar'), 'FOO preserved');
1216
+ assert.ok(content.includes('BAZ=qux'), 'BAZ preserved');
1217
+ assert.ok(content.includes('WORKATO_API_TOKEN=updated'), 'token updated');
1218
+ } finally {
1219
+ fs.rmSync(dir, { recursive: true, force: true });
1220
+ }
1221
+ });
1222
+ });
1223
+
985
1224
  // Restore console at end
986
1225
  process.on('exit', () => {
987
1226
  console.log = origLog;