@fieldwork-ai/cli 0.16.0 → 0.17.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.
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function tablesCommand(): Command;
@@ -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,15 @@ 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
- fail(body?.error ?? `request failed (${res.status})`, exitCodeFor(res.status));
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
+ const prefix = typeof body?.tool === "string" ? `${body.tool}: ` : "";
125
+ fail([prefix + message, ...details].join("\n"), exitCodeFor(res.status));
118
126
  }
119
127
  return body;
120
128
  }
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.16.0",
3
+ "version": "0.17.1",
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
+ }