@fieldwork-ai/cli 0.16.0 → 0.17.2
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/README.md +4 -0
- package/dist/commands/automations.js +20 -1
- package/dist/commands/tables.d.ts +2 -0
- package/dist/commands/tables.js +41 -0
- package/dist/http.js +19 -1
- package/dist/index.js +2 -0
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -62,3 +62,7 @@ claude mcp add --transport http fieldwork https://app.getfieldwork.ai/mcp
|
|
|
62
62
|
Source stays in the private app repository under `cli/`. Run `pnpm install --frozen-lockfile`, `pnpm build`, and `pnpm test` here. Publish the compiled package with `npm publish --access public`; the package includes `dist/` and npm metadata, without TypeScript source or source maps. `scripts/pack-cli.sh` builds and packs this same package into the compute image without depending on an npm release.
|
|
63
63
|
|
|
64
64
|
The terminal chat client is the separate public `@fieldwork-ai/fieldwork-code` package (`fwcode`).
|
|
65
|
+
|
|
66
|
+
## Graph authoring diagnostics
|
|
67
|
+
|
|
68
|
+
Use `fieldwork tool run automations_graph_examples --json` for executable examples and the coordinated code/graph publication format. `automations_graph_validate` takes the publication input without committing it. `automations_graph_test` uses isolated editable-table fixtures and captured notifications; it does not execute authored code or send email. `automations_graph_wait` returns bounded, compact progress, and `automations_graph_trace` accepts `detail: "full"` when saved payloads are needed. Contract failures include source/destination paths, required coordinated changes and retryability on stderr with a nonzero exit code.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { api, apiJson, getContext, requireOrgId } from "../http.js";
|
|
2
3
|
import { EXIT, confirm, exitCodeFor, fail, note, printData } from "../output.js";
|
|
3
|
-
const TERMINAL_STATUSES = new Set(["succeeded", "failed"]);
|
|
4
|
+
const TERMINAL_STATUSES = new Set(["succeeded", "failed", "cancelled", "needs_attention"]);
|
|
4
5
|
async function resolveAutomation(name) {
|
|
5
6
|
const body = await apiJson(`/api/organizations/${requireOrgId()}/automations?name=${encodeURIComponent(name)}`);
|
|
6
7
|
const automation = body.automations?.[0];
|
|
@@ -70,6 +71,24 @@ async function followRun(automationId, runId, intervalSeconds, json) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
export function registerAutomationCommands(program) {
|
|
74
|
+
const graph = program.command("automation-graph").description("Publish and inspect automation graphs");
|
|
75
|
+
graph.command("get <automation-id>").action(async (id) => printData(await apiJson(`/api/organizations/${requireOrgId()}/automations/${encodeURIComponent(id)}/graph`), true));
|
|
76
|
+
graph.command("trace <automation-id> <run-id>").action(async (id, runId) => printData(await apiJson(`/api/organizations/${requireOrgId()}/automations/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/steps`), true));
|
|
77
|
+
graph.command("configure <automation-id>").requiredOption("--file <path>").action(async (id, options) => {
|
|
78
|
+
const body = await readFile(options.file, "utf8");
|
|
79
|
+
await confirm("Apply these triggers and enabled state?", getContext().yes);
|
|
80
|
+
printData(await apiJson(`/api/organizations/${requireOrgId()}/automations/${encodeURIComponent(id)}/graph`, { method: "PATCH", body }), true);
|
|
81
|
+
});
|
|
82
|
+
graph.command("control <automation-id> <run-id>").requiredOption("--file <path>", "Cancel, retry, or confirmed reconciliation").action(async (id, runId, options) => {
|
|
83
|
+
const body = await readFile(options.file, "utf8");
|
|
84
|
+
await confirm("Apply this run control? Confirm external results before reconciliation.", getContext().yes);
|
|
85
|
+
printData(await apiJson(`/api/organizations/${requireOrgId()}/automations/${encodeURIComponent(id)}/runs/${encodeURIComponent(runId)}/steps`, { method: "POST", body }), true);
|
|
86
|
+
});
|
|
87
|
+
graph.command("publish").requiredOption("--file <path>").action(async (options) => {
|
|
88
|
+
const body = await readFile(options.file, "utf8");
|
|
89
|
+
await confirm("Publish this graph and delegate its listed resource access?", getContext().yes);
|
|
90
|
+
printData(await apiJson(`/api/organizations/${requireOrgId()}/automations/graphs`, { method: "POST", body }), true);
|
|
91
|
+
});
|
|
73
92
|
const automations = program
|
|
74
93
|
.command("automations")
|
|
75
94
|
.description("Standing instructions that run unattended (schedules, webhooks)");
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { apiJson, requireOrgId, getContext } from "../http.js";
|
|
4
|
+
import { confirm, printData } from "../output.js";
|
|
5
|
+
export function tablesCommand() {
|
|
6
|
+
const command = new Command("tables").description("Fieldwork Tables and editable records");
|
|
7
|
+
const base = () => `/api/organizations/${requireOrgId()}/tables`;
|
|
8
|
+
command
|
|
9
|
+
.command("list")
|
|
10
|
+
.action(async () => printData(await apiJson(base()), true));
|
|
11
|
+
command
|
|
12
|
+
.command("create")
|
|
13
|
+
.requiredOption("--file <path>", "Table definition file")
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const body = await readFile(options.file, "utf8");
|
|
16
|
+
await confirm("Create this Fieldwork Table?", getContext().yes);
|
|
17
|
+
printData(await apiJson(base(), { method: "POST", body }), true);
|
|
18
|
+
});
|
|
19
|
+
command
|
|
20
|
+
.command("records <table-id>")
|
|
21
|
+
.option("--after <id>")
|
|
22
|
+
.option("--limit <number>", "Page size", "50")
|
|
23
|
+
.action(async (tableId, options) => {
|
|
24
|
+
const query = new URLSearchParams({ limit: options.limit });
|
|
25
|
+
if (options.after)
|
|
26
|
+
query.set("after", options.after);
|
|
27
|
+
printData(await apiJson(`${base()}/${encodeURIComponent(tableId)}/records?${query}`), true);
|
|
28
|
+
});
|
|
29
|
+
command
|
|
30
|
+
.command("mutate <table-id>")
|
|
31
|
+
.requiredOption("--file <path>", "Record mutation file")
|
|
32
|
+
.action(async (tableId, options) => {
|
|
33
|
+
const body = await readFile(options.file, "utf8");
|
|
34
|
+
await confirm("Apply this record mutation?", getContext().yes);
|
|
35
|
+
printData(await apiJson(`${base()}/${encodeURIComponent(tableId)}/records`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
body,
|
|
38
|
+
}), true);
|
|
39
|
+
});
|
|
40
|
+
return command;
|
|
41
|
+
}
|
package/dist/http.js
CHANGED
|
@@ -114,7 +114,25 @@ export async function apiJson(pathname, init = {}) {
|
|
|
114
114
|
const res = await api(pathname, init);
|
|
115
115
|
const body = (await res.json().catch(() => null));
|
|
116
116
|
if (!res.ok) {
|
|
117
|
-
|
|
117
|
+
const message = body?.error ?? `request failed (${res.status})`;
|
|
118
|
+
const details = Array.isArray(body?.details) ? body.details.flatMap((issue) => {
|
|
119
|
+
if (!issue || typeof issue !== "object")
|
|
120
|
+
return [];
|
|
121
|
+
const { path, message } = issue;
|
|
122
|
+
return typeof message === "string" ? [`${Array.isArray(path) ? path.join(".") + ": " : ""}${message}`] : [];
|
|
123
|
+
}) : [];
|
|
124
|
+
if (body?.code === "invalid_graph" && body.details && typeof body.details === "object" && !Array.isArray(body.details)) {
|
|
125
|
+
const diagnostic = body.details;
|
|
126
|
+
for (const key of ["action_definition_id", "automation_id", "automation_revision_id", "node_id", "source", "destination", "input", "output", "source_schema", "target_schema", "effect_changed", "credential_changed", "required_action"]) {
|
|
127
|
+
const value = diagnostic[key];
|
|
128
|
+
if (value !== undefined && value !== null)
|
|
129
|
+
details.push(`${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
130
|
+
}
|
|
131
|
+
if (typeof body.retryable === "boolean")
|
|
132
|
+
details.push(`retryable: ${body.retryable}`);
|
|
133
|
+
}
|
|
134
|
+
const prefix = typeof body?.tool === "string" ? `${body.tool}: ` : "";
|
|
135
|
+
fail([prefix + message, ...details].join("\n"), exitCodeFor(res.status));
|
|
118
136
|
}
|
|
119
137
|
return body;
|
|
120
138
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { tablesCommand } from "./commands/tables.js";
|
|
2
3
|
import { Command } from "commander";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { initContext } from "./http.js";
|
|
@@ -40,6 +41,7 @@ registerAppCommands(program);
|
|
|
40
41
|
registerAutomationCommands(program);
|
|
41
42
|
registerTaskCommands(program);
|
|
42
43
|
registerConnectorCommands(program);
|
|
44
|
+
program.addCommand(tablesCommand());
|
|
43
45
|
program.parseAsync(process.argv).catch((err) => {
|
|
44
46
|
process.stderr.write(`fieldwork: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
45
47
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fieldwork-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.2",
|
|
4
4
|
"description": "Fieldwork asset-management CLI",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=24"
|
|
15
15
|
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "node -e \"require('node:fs').rmSync('dist', {recursive:true,force:true})\" && tsc",
|
|
18
|
+
"prepack": "pnpm build",
|
|
19
|
+
"dev": "tsx src/index.ts",
|
|
20
|
+
"test": "vitest run"
|
|
21
|
+
},
|
|
16
22
|
"dependencies": {
|
|
17
23
|
"commander": "^14.0.2"
|
|
18
24
|
},
|
|
@@ -30,10 +36,5 @@
|
|
|
30
36
|
"exports": {
|
|
31
37
|
"./package.json": "./package.json",
|
|
32
38
|
"./*": "./dist/*.js"
|
|
33
|
-
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "node -e \"require('node:fs').rmSync('dist', {recursive:true,force:true})\" && tsc",
|
|
36
|
-
"dev": "tsx src/index.ts",
|
|
37
|
-
"test": "vitest run"
|
|
38
39
|
}
|
|
39
|
-
}
|
|
40
|
+
}
|