workato-dev-api 1.0.0 → 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.
package/CLAUDE.md ADDED
@@ -0,0 +1,153 @@
1
+ # Workato Dev Environment — Claude Notes
2
+
3
+ ## CLI: `workato-dev-api`
4
+
5
+ Use `npx workato-dev-api <command>` (or `workato <command>` if installed globally) for all Workato operations — reads and writes alike.
6
+
7
+ Requires `WORKATO_API_TOKEN` in a `.env` file (cwd `.env` takes highest priority). Base URL: `https://app.trial.workato.com/api`.
8
+
9
+ ### Commands
10
+
11
+ | Command | Description |
12
+ |---|---|
13
+ | `workato get <recipe_id>` | Fetch recipe code JSON → saved to `recipe_<id>_code.json` |
14
+ | `workato list-recipes` | List recipes. Filters: `--folder <id>`, `--project <id>`, `--page <n>` |
15
+ | `workato list-projects` | List all projects |
16
+ | `workato list-folders` | List folders. Filter: `--parent <id>` |
17
+ | `workato list-connections` | List connections. Filter: `--folder <id>` |
18
+ | `workato list-data-tables` | List data tables. Filter: `--project <id>` |
19
+ | `workato get-data-table <id>` | Fetch data table schema and details |
20
+ | `workato get-jobs <recipe_id>` | List recent jobs. Filters: `--limit <n>`, `--status <status>` |
21
+ | `workato get-job <recipe_id> <job_id>` | Fetch a single job |
22
+ | `workato create "<name>" <code.json>` | Create a recipe from a full code JSON file |
23
+ | `workato create-api-trigger "<name>"` | Create a recipe with a bare API Platform trigger |
24
+ | `workato update-step <recipe_id> <step_as_id> <patch.json>` | Deep-merge a patch into one step (by `as` ID) |
25
+ | `workato put-code <recipe_id> <code.json>` | Replace entire recipe code |
26
+ | `workato start <recipe_id>` | Start a recipe |
27
+ | `workato stop <recipe_id>` | Stop a recipe |
28
+ | `workato delete <recipe_id>` | Delete a recipe |
29
+
30
+ ### How `update-step` works
31
+ - Fetches current recipe code
32
+ - Finds the step whose `as` field matches `<step_as_id>` (searches recursively into nested `block` arrays; trigger is the top-level code object)
33
+ - Deep-merges the patch JSON into that step (objects merged, arrays/primitives replaced)
34
+ - PUTs the full updated code back
35
+
36
+ ---
37
+
38
+ ## Recipe Code Structure
39
+
40
+ A recipe's `code` field is a JSON-stringified object. The top-level object is the **trigger** step; action steps live in `code.block[]`.
41
+
42
+ ```json
43
+ {
44
+ "number": 0,
45
+ "provider": "<provider>",
46
+ "name": "<action_name>",
47
+ "as": "<8-char-hex>", // unique step ID used for wiring
48
+ "keyword": "trigger", // "trigger" | "action" | "if" | "foreach" etc.
49
+ "dynamicPickListSelection": {},
50
+ "toggleCfg": {},
51
+ "input": { ... }, // field values and wiring go here
52
+ "extended_output_schema": [...], // THIS is the output schema — must be specified manually
53
+ "extended_input_schema": [...], // describes available input fields (e.g. data table columns) — must be specified manually
54
+ "block": [...], // nested steps
55
+ "uuid": "<uuid>",
56
+ "title": null,
57
+ "description": null
58
+ }
59
+ ```
60
+
61
+ There is no non-extended `output_schema` / `input_schema` — `extended_output_schema` and `extended_input_schema` are the only schema fields. For some built-in connector actions (e.g. OpenAI `transcription`), the output schema is known server-side and the step can omit `extended_output_schema` entirely — downstream steps can still wire from it using the datapill syntax. For custom/dynamic steps (e.g. API Platform trigger, Data Table), you must provide these schemas explicitly.
62
+
63
+ The recipe also has a separate `config` array (JSON-stringified) listing which connections are used:
64
+ ```json
65
+ [
66
+ { "keyword": "application", "name": "<provider>", "provider": "<provider>", "skip_validation": false, "account_id": <connection_id_or_null> }
67
+ ]
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Field Wiring Syntax
73
+
74
+ All field values in a step's `input` object use one of three forms:
75
+
76
+ ### 1. Datapill — reference another step's output
77
+
78
+ ```
79
+ "#{_dp('{\"pill_type\":\"output\",\"provider\":\"<provider>\",\"line\":\"<step_as_id>\",\"path\":[\"<field1>\",\"<field2>\"]}')}
80
+ ```
81
+
82
+ - `provider`: the provider of the source step (e.g. `workato_api_platform`, `open_ai`, `workato_db_table`, `google_drive`)
83
+ - `line`: the `as` value (8-char hex) of the source step
84
+ - `path`: JSON array of strings navigating the output schema
85
+
86
+ **Example — wire trigger's `request.file_content` into an OpenAI step:**
87
+ ```json
88
+ "file_content": "#{_dp('{\"pill_type\":\"output\",\"provider\":\"workato_api_platform\",\"line\":\"8f52532b\",\"path\":[\"request\",\"file_content\"]}')}
89
+ ```
90
+
91
+ **Example — wire OpenAI step's `text` output into a data table column:**
92
+ ```json
93
+ "03886fe9_176d_4bdd_9296_d48219b345c8": "#{_dp('{\"pill_type\":\"output\",\"provider\":\"open_ai\",\"line\":\"5df21cfd\",\"path\":[\"text\"]}')}
94
+ ```
95
+
96
+ ### 2. Formula mode — Workato functions/expressions
97
+
98
+ Prefix the value with `=` inside the string:
99
+
100
+ ```
101
+ "=\"#{now}\"" // current timestamp
102
+ "=\"#{uuid}\"" // generate a UUID
103
+ ```
104
+
105
+ ### 3. Static literal
106
+
107
+ Plain string value — no special syntax:
108
+ ```json
109
+ "language": "en",
110
+ "table_id": "3512"
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Data Table Column Names
116
+
117
+ Data table column field names in `input.parameters` are **UUID-style strings with underscores**, not human-readable names. You must fetch the recipe's existing code (or the data table schema) to get the correct column key for each field.
118
+
119
+ Example from Audio Transcripts table:
120
+ - `transcript_text` → `03886fe9_176d_4bdd_9296_d48219b345c8`
121
+ - `transcribed_at` → `aa4e76dd_0de1_4f0a_946b_fb0a4e1ecff5`
122
+ - `file_name` → `33ee499b_51cc_4ddf_ba03_6a5b8eca79f5`
123
+ - `file_id` → `0f05f324_040d_4d11_b201_05afaa850729`
124
+ - `recorded_at` → `aefc6da4_93a5_40e9_b933_13c2cac095ac`
125
+
126
+ Always use `workato get-data-table <id>` or read the recipe code to look up the actual column UUIDs before wiring.
127
+
128
+ ---
129
+
130
+ ## Reference Recipe
131
+
132
+ **Transcribe Audio** (ID: 167603) — `https://app.trial.workato.com/recipes/167603-transcribe-audio`
133
+
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.
135
+
136
+ Trigger `as`: `8f52532b` | OpenAI step `as`: `5df21cfd` | Data Table step `as`: `1614a36d`
137
+
138
+ ---
139
+
140
+ ## Current Project
141
+
142
+ **"Get Audio Transcript"** — Project ID: `14318`, Folder ID: `20245`
143
+
144
+ Key recipe IDs:
145
+ - `167582` — Get Audio Transcript (callable, RecipeOps trigger)
146
+ - `167583` — Audio File Orchestrator (Google Drive trigger)
147
+ - `167603` — Transcribe Audio (API Platform trigger, reference recipe)
148
+
149
+ Key connection IDs:
150
+ - OpenAI: `14358`
151
+ - RecipeOps: `14233`
152
+
153
+ Data Table: **Audio Transcripts** (ID: `3512`) in project `14318`.
package/README.md CHANGED
@@ -29,8 +29,28 @@ WORKATO_API_TOKEN=your_token_here
29
29
 
30
30
  You can also export it directly in your shell environment.
31
31
 
32
+ ## Claude Code setup
33
+
34
+ In a clean working directory, run these two commands once before starting Claude Code:
35
+
36
+ ```sh
37
+ npx workato-dev-api auth YOUR_API_TOKEN
38
+ npx workato-dev-api bootstrap-claude
39
+ ```
40
+
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 just open Claude Code in that directory and start working.
44
+
32
45
  ## Commands
33
46
 
47
+ ### Setup
48
+
49
+ | Command | Description |
50
+ |---|---|
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 |
53
+
34
54
  ### Read
35
55
 
36
56
  | Command | Description |
package/cli.js CHANGED
@@ -5,6 +5,7 @@ const os = require('os');
5
5
  const path = require('path');
6
6
  const {
7
7
  loadEnv,
8
+ cmdBootstrapClaude, cmdAuth,
8
9
  cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
9
10
  cmdListConnections, cmdListDataTables, cmdGetDataTable,
10
11
  cmdGetJobs, cmdGetJob,
@@ -12,6 +13,22 @@ const {
12
13
  cmdStart, cmdStop, cmdDelete,
13
14
  } = require('./lib');
14
15
 
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());
29
+ process.exit(0);
30
+ }
31
+
15
32
  // Load order — last one wins, so highest-priority sources go last:
16
33
  // package dir → home dir → cwd (cwd always wins)
17
34
  loadEnv(path.join(__dirname, '.env'));
@@ -19,7 +36,7 @@ loadEnv(path.join(os.homedir(), '.env'));
19
36
  loadEnv(path.join(process.cwd(), '.env'));
20
37
 
21
38
  if (!process.env.WORKATO_API_TOKEN) {
22
- 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');
23
40
  process.exit(1);
24
41
  }
25
42
 
@@ -49,6 +66,10 @@ function usage() {
49
66
  console.error(`
50
67
  workato <command> [options]
51
68
 
69
+ Setup:
70
+ bootstrap-claude Copy CLAUDE.md into the current directory
71
+ auth <token> Save API token to .env in the current directory
72
+
52
73
  Read commands:
53
74
  get <recipe_id> Fetch recipe code → recipe_<id>_code.json
54
75
  list-recipes [--folder <id>] [--project <id>] [--page <n>]
package/lib.js CHANGED
@@ -143,6 +143,30 @@ function apiTriggerConfig() {
143
143
  ];
144
144
  }
145
145
 
146
+ // ── Setup commands ────────────────────────────────────────────────────────────
147
+
148
+ function cmdBootstrapClaude(destDir) {
149
+ const src = path.join(__dirname, 'CLAUDE.md');
150
+ const dest = path.join(destDir ?? process.cwd(), 'CLAUDE.md');
151
+ fs.copyFileSync(src, dest);
152
+ console.log(`CLAUDE.md written to ${dest}`);
153
+ return dest;
154
+ }
155
+
156
+ function cmdAuth(token, destDir) {
157
+ const envPath = path.join(destDir ?? process.cwd(), '.env');
158
+ let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
159
+ const line = `WORKATO_API_TOKEN=${token}`;
160
+ if (/^WORKATO_API_TOKEN=/m.test(content)) {
161
+ content = content.replace(/^WORKATO_API_TOKEN=.*/m, line);
162
+ } else {
163
+ content = content ? content.trimEnd() + '\n' + line + '\n' : line + '\n';
164
+ }
165
+ fs.writeFileSync(envPath, content);
166
+ console.log(`Token saved to ${envPath}`);
167
+ return envPath;
168
+ }
169
+
146
170
  // ── Read commands ─────────────────────────────────────────────────────────────
147
171
 
148
172
  async function cmdGet(recipeId) {
@@ -313,6 +337,8 @@ module.exports = {
313
337
  // helpers
314
338
  findStep, deepMerge, extractCode,
315
339
  apiTriggerCode, apiTriggerConfig,
340
+ // setup commands
341
+ cmdBootstrapClaude, cmdAuth,
316
342
  // read commands
317
343
  cmdGet, cmdListRecipes, cmdListProjects, cmdListFolders,
318
344
  cmdListConnections, cmdListDataTables, cmdGetDataTable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workato-dev-api",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "CLI for the Workato Developer API — recipes, connections, data tables, and more",
5
5
  "bin": {
6
6
  "workato": "cli.js"
package/test/cli.test.js CHANGED
@@ -982,6 +982,144 @@ describe('cmdUpdateStep — deeply nested step', () => {
982
982
  });
983
983
  });
984
984
 
985
+ // ── cmdBootstrapClaude ────────────────────────────────────────────────────────
986
+
987
+ describe('cmdBootstrapClaude', () => {
988
+ function tmpDir() {
989
+ const d = path.join(os.tmpdir(), `workato-test-${Date.now()}-${Math.random()}`);
990
+ fs.mkdirSync(d, { recursive: true });
991
+ return d;
992
+ }
993
+
994
+ test('copies CLAUDE.md into the destination directory', () => {
995
+ const dir = tmpDir();
996
+ try {
997
+ lib.cmdBootstrapClaude(dir);
998
+ assert.ok(fs.existsSync(path.join(dir, 'CLAUDE.md')), 'CLAUDE.md should exist in dest dir');
999
+ } finally {
1000
+ fs.rmSync(dir, { recursive: true, force: true });
1001
+ }
1002
+ });
1003
+
1004
+ test('written file content matches the source CLAUDE.md', () => {
1005
+ const dir = tmpDir();
1006
+ try {
1007
+ lib.cmdBootstrapClaude(dir);
1008
+ const srcPath = path.join(__dirname, '..', 'CLAUDE.md');
1009
+ const src = fs.readFileSync(srcPath, 'utf8');
1010
+ const dest = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
1011
+ assert.equal(dest, src);
1012
+ } finally {
1013
+ fs.rmSync(dir, { recursive: true, force: true });
1014
+ }
1015
+ });
1016
+
1017
+ test('returns the destination file path', () => {
1018
+ const dir = tmpDir();
1019
+ try {
1020
+ const result = lib.cmdBootstrapClaude(dir);
1021
+ assert.equal(result, path.join(dir, 'CLAUDE.md'));
1022
+ } finally {
1023
+ fs.rmSync(dir, { recursive: true, force: true });
1024
+ }
1025
+ });
1026
+
1027
+ test('overwrites an existing CLAUDE.md', () => {
1028
+ const dir = tmpDir();
1029
+ try {
1030
+ fs.writeFileSync(path.join(dir, 'CLAUDE.md'), 'old content');
1031
+ lib.cmdBootstrapClaude(dir);
1032
+ const content = fs.readFileSync(path.join(dir, 'CLAUDE.md'), 'utf8');
1033
+ assert.notEqual(content, 'old content');
1034
+ } finally {
1035
+ fs.rmSync(dir, { recursive: true, force: true });
1036
+ }
1037
+ });
1038
+ });
1039
+
1040
+ // ── cmdAuth ───────────────────────────────────────────────────────────────────
1041
+
1042
+ describe('cmdAuth', () => {
1043
+ function tmpDir() {
1044
+ const d = path.join(os.tmpdir(), `workato-auth-test-${Date.now()}-${Math.random()}`);
1045
+ fs.mkdirSync(d, { recursive: true });
1046
+ return d;
1047
+ }
1048
+
1049
+ test('creates .env with token when file does not exist', () => {
1050
+ const dir = tmpDir();
1051
+ try {
1052
+ lib.cmdAuth('mytoken123', dir);
1053
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1054
+ assert.ok(content.includes('WORKATO_API_TOKEN=mytoken123'));
1055
+ } finally {
1056
+ fs.rmSync(dir, { recursive: true, force: true });
1057
+ }
1058
+ });
1059
+
1060
+ test('returns the .env file path', () => {
1061
+ const dir = tmpDir();
1062
+ try {
1063
+ const result = lib.cmdAuth('tok', dir);
1064
+ assert.equal(result, path.join(dir, '.env'));
1065
+ } finally {
1066
+ fs.rmSync(dir, { recursive: true, force: true });
1067
+ }
1068
+ });
1069
+
1070
+ test('updates existing WORKATO_API_TOKEN line in .env', () => {
1071
+ const dir = tmpDir();
1072
+ try {
1073
+ fs.writeFileSync(path.join(dir, '.env'), 'WORKATO_API_TOKEN=oldtoken\n');
1074
+ lib.cmdAuth('newtoken', dir);
1075
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1076
+ assert.ok(content.includes('WORKATO_API_TOKEN=newtoken'), 'should have new token');
1077
+ assert.ok(!content.includes('oldtoken'), 'should not have old token');
1078
+ assert.equal((content.match(/WORKATO_API_TOKEN=/g) || []).length, 1, 'only one token line');
1079
+ } finally {
1080
+ fs.rmSync(dir, { recursive: true, force: true });
1081
+ }
1082
+ });
1083
+
1084
+ test('appends token to .env that has other keys but no WORKATO_API_TOKEN', () => {
1085
+ const dir = tmpDir();
1086
+ try {
1087
+ fs.writeFileSync(path.join(dir, '.env'), 'OTHER_KEY=value\n');
1088
+ lib.cmdAuth('mytoken', dir);
1089
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1090
+ assert.ok(content.includes('OTHER_KEY=value'), 'existing key preserved');
1091
+ assert.ok(content.includes('WORKATO_API_TOKEN=mytoken'), 'token appended');
1092
+ } finally {
1093
+ fs.rmSync(dir, { recursive: true, force: true });
1094
+ }
1095
+ });
1096
+
1097
+ test('token value containing = is preserved verbatim', () => {
1098
+ const dir = tmpDir();
1099
+ try {
1100
+ lib.cmdAuth('tok==extra==', dir);
1101
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1102
+ assert.ok(content.includes('WORKATO_API_TOKEN=tok==extra=='));
1103
+ } finally {
1104
+ fs.rmSync(dir, { recursive: true, force: true });
1105
+ }
1106
+ });
1107
+
1108
+ test('preserves other keys in .env when updating token', () => {
1109
+ const dir = tmpDir();
1110
+ try {
1111
+ fs.writeFileSync(path.join(dir, '.env'), 'FOO=bar\nWORKATO_API_TOKEN=old\nBAZ=qux\n');
1112
+ lib.cmdAuth('updated', dir);
1113
+ const content = fs.readFileSync(path.join(dir, '.env'), 'utf8');
1114
+ assert.ok(content.includes('FOO=bar'), 'FOO preserved');
1115
+ assert.ok(content.includes('BAZ=qux'), 'BAZ preserved');
1116
+ assert.ok(content.includes('WORKATO_API_TOKEN=updated'), 'token updated');
1117
+ } finally {
1118
+ fs.rmSync(dir, { recursive: true, force: true });
1119
+ }
1120
+ });
1121
+ });
1122
+
985
1123
  // Restore console at end
986
1124
  process.on('exit', () => {
987
1125
  console.log = origLog;