automate-it 0.2.0 → 0.4.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.
Files changed (2) hide show
  1. package/ait.mjs +152 -34
  2. package/package.json +1 -1
package/ait.mjs CHANGED
@@ -3,10 +3,15 @@
3
3
  //
4
4
  // Zero-dependency ESM script (Node 18+ or Bun). Speaks the Model Context
5
5
  // Protocol (StreamableHTTP, stateless) to Automate It's public MCP server,
6
- // authenticating with an API key (ak_*) so the CLI can do everything a
7
- // remote MCP client can, headlessly. `ait call <tool>` passes any MCP tool
8
- // through verbatim; the named commands are ergonomic wrappers over the
9
- // common ones. Every command goes through MCP there is no REST path.
6
+ // authenticating with an API key (ak_*). Every command goes through MCP
7
+ // there is no REST path.
8
+ //
9
+ // The command surface is a closed set: every tool the workspace MCP server
10
+ // exposes has exactly one named command here, and there is no generic
11
+ // passthrough. Adding a tool to the server means adding its command here —
12
+ // that's deliberate, so the skill's capabilities can be read off its docs.
13
+ // Scopes and workspace role still bound what runs, enforced server-side, and
14
+ // tools that destroy data additionally require an explicit --yes.
10
15
  //
11
16
  // Tool output is printed verbatim on stdout; errors go to stderr as
12
17
  // {"error": "..."} with exit code 1.
@@ -26,12 +31,14 @@ Environment:
26
31
  AUTOMATE_IT_WORKSPACE Default workspace id (else pass --workspace, else
27
32
  auto-resolved when the key has exactly one workspace)
28
33
 
34
+ Destructive commands (task delete, task delete-content, task clear-content,
35
+ automation delete, folders delete) permanently remove data and refuse to run
36
+ without --yes. Only pass it when the operator asked for that deletion by name.
37
+
29
38
  Discovery:
30
- ait tools List every available MCP tool
31
- ait call <tool> [--args '<json>'] Call any MCP tool directly (workspaceId
32
- is injected automatically if omitted)
33
39
  ait workspaces List workspaces the key can access
34
40
  ait whoami Show the user this key acts as
41
+ ait integrations Platforms connected for publishing
35
42
 
36
43
  Tasks (the review-gate loop):
37
44
  ait task create --title <t> Create a task
@@ -64,8 +71,9 @@ Tasks (the review-gate loop):
64
71
  [--media '<json array>'] [--sort-order <n>]
65
72
  ait task update-content <taskId> <contentItemId>
66
73
  [--body <text>] [--title <t>] Rewrite a content item in place
67
- ait task delete-content <taskId> <contentItemId>
68
- ait task clear-content <taskId> Remove every content item from a task
74
+ ait task delete-content <taskId> <contentItemId> --yes
75
+ ait task clear-content <taskId> --yes
76
+ Remove every content item from a task
69
77
  ait task complete <taskId> Finish work (status → review/approved)
70
78
  ait task comment <taskId> --comment <text>
71
79
  Leave a note for the human reviewer
@@ -78,7 +86,7 @@ Tasks (the review-gate loop):
78
86
  later; --auto picks the next open slot
79
87
  per the workspace's scheduling rules;
80
88
  --clear returns it to manual
81
- ait task delete <taskId>
89
+ ait task delete <taskId> --yes
82
90
 
83
91
  Skills (workspace voice, formatting rules, bundled reference files):
84
92
  ait skills list id, name, description for each skill
@@ -91,7 +99,9 @@ Automations:
91
99
  ait automation get <automationId>
92
100
  ait automation update <automationId> [--name <n>] [--instructions <text>]
93
101
  [--output-types x,...] [--schedule <json>]
94
- ait automation delete <automationId>
102
+ ait automation delete <automationId> --yes
103
+ Also deletes every task it spawned,
104
+ published ones included
95
105
  ait automation run <automationId> Trigger now; returns the spawned task
96
106
 
97
107
  Files:
@@ -100,13 +110,55 @@ Files:
100
110
  ait files list [--search <s>] [--mime-type <m>] [--limit <n>]
101
111
  ait files download-url <fileId> [--expires-in <seconds>]
102
112
  Presigned URL — download it yourself
103
- (folders/integrations: use \`ait call\` see \`ait tools\`)
113
+ ait files move <fileId> [--folder <folderId>]
114
+ Move into a folder; omit --folder for
115
+ the workspace root
116
+ ait files copy <fileId> [--folder <folderId>]
117
+ Duplicate the file (new file + object)
118
+
119
+ Folders:
120
+ ait folders list [--parent <folderId>] [--root]
121
+ --root lists top-level folders only
122
+ ait folders create --name <n> [--parent <folderId>]
123
+ ait folders rename <folderId> --name <n>
124
+ ait folders delete <folderId> --yes Also deletes every file and nested
125
+ folder inside it
126
+
127
+ Composing posts:
128
+ ait limits [--platform <p>] Text limits for every platform, or one.
129
+ [--text <draft>] With --text, returns the draft's length
130
+ under that platform's counting rule and
131
+ whether it fits. Platforms count
132
+ differently: X counts a URL as 23 and
133
+ emoji as 2, Threads counts emoji as
134
+ UTF-8 bytes, Bluesky counts graphemes.
135
+ ait shorten <url> [--title <t>] Shorten a link for a post
104
136
 
105
137
  All workspace commands accept --workspace <id>.`;
106
138
 
107
139
  export class CliError extends Error {}
108
140
 
109
- const BOOLEAN_FLAGS = new Set(["no-review", "claim", "mine", "clear", "auto", "help"]);
141
+ const BOOLEAN_FLAGS = new Set(["no-review", "claim", "mine", "clear", "auto", "help", "yes", "root"]);
142
+
143
+ // MCP tools that permanently remove data, keyed to what they destroy. Keyed by
144
+ // tool rather than by command so the gate lives next to the thing that does
145
+ // the damage — a second command routing to one of these inherits it.
146
+ const DESTRUCTIVE_TOOLS = new Map([
147
+ ["delete_task", "delete the task and everything attached to it"],
148
+ ["delete_content_item", "delete the content item"],
149
+ ["clear_task_content", "remove every content item from the task"],
150
+ ["delete_automation", "delete the automation and every task it ever spawned, published ones included"],
151
+ ["delete_folder", "delete the folder and every file and folder inside it"],
152
+ ]);
153
+
154
+ /** Destructive tools need an explicit --yes; nothing else is affected. */
155
+ function requireConfirmation(flags, toolName) {
156
+ const effect = DESTRUCTIVE_TOOLS.get(toolName);
157
+ if (!effect || flags.yes === true) return;
158
+ throw new CliError(
159
+ `Refusing to run without --yes: this would ${effect}. It cannot be undone — confirm with the operator, then re-run with --yes.`
160
+ );
161
+ }
110
162
 
111
163
  export function parseArgs(argv) {
112
164
  const positional = [];
@@ -342,28 +394,19 @@ export async function runCommand(argv, opts = {}) {
342
394
  return USAGE;
343
395
  }
344
396
 
345
- const SINGLE_WORD_COMMANDS = new Set(["tools", "call", "workspaces", "whoami", "upload-url"]);
397
+ const SINGLE_WORD_COMMANDS = new Set([
398
+ "workspaces",
399
+ "whoami",
400
+ "upload-url",
401
+ "integrations",
402
+ "limits",
403
+ "shorten",
404
+ ]);
346
405
  const command = SINGLE_WORD_COMMANDS.has(resource)
347
406
  ? resource
348
407
  : [resource, action].filter(Boolean).join(" ");
349
408
 
350
409
  switch (command) {
351
- case "tools": {
352
- const result = await mcpRequest(ctx, "tools/list", {});
353
- return (result?.tools ?? [])
354
- .map((t) => `${t.name} — ${(t.description || "").split("\n")[0]}`)
355
- .join("\n");
356
- }
357
-
358
- case "call": {
359
- const toolName = requirePositional([action, ...rest].filter(Boolean), "tool", "ait call <tool> [--args '<json>']");
360
- const args = flags.args ? parseJsonFlag(flags.args, "args") : {};
361
- if (args.workspaceId === undefined && toolName !== "list_workspaces" && toolName !== "get_clerk_user_data") {
362
- args.workspaceId = await resolveWorkspace(ctx, flags);
363
- }
364
- return callTool(ctx, toolName, args);
365
- }
366
-
367
410
  case "workspaces":
368
411
  return callTool(ctx, "list_workspaces", {});
369
412
 
@@ -448,6 +491,7 @@ export async function runCommand(argv, opts = {}) {
448
491
  delete: "delete_task",
449
492
  };
450
493
  const taskId = requirePositional(rest, "taskId", `ait task ${action} <taskId>`);
494
+ requireConfirmation(flags, toolByAction[action]);
451
495
  const workspaceId = await resolveWorkspace(ctx, flags);
452
496
  return callTool(ctx, toolByAction[action], { workspaceId, taskId });
453
497
  }
@@ -536,8 +580,9 @@ export async function runCommand(argv, opts = {}) {
536
580
  case "automation get":
537
581
  case "automation delete": {
538
582
  const automationId = requirePositional(rest, "automationId", `ait automation ${action} <automationId>`);
539
- const workspaceId = await resolveWorkspace(ctx, flags);
540
583
  const tool = action === "get" ? "get_automation" : "delete_automation";
584
+ requireConfirmation(flags, tool);
585
+ const workspaceId = await resolveWorkspace(ctx, flags);
541
586
  return callTool(ctx, tool, { workspaceId, automationId });
542
587
  }
543
588
 
@@ -590,15 +635,17 @@ export async function runCommand(argv, opts = {}) {
590
635
  }
591
636
 
592
637
  case "task delete-content": {
593
- const usage = "ait task delete-content <taskId> <contentItemId>";
638
+ const usage = "ait task delete-content <taskId> <contentItemId> --yes";
594
639
  const taskId = requirePositionalAt(rest, 0, "taskId", usage);
595
640
  const contentItemId = requirePositionalAt(rest, 1, "contentItemId", usage);
641
+ requireConfirmation(flags, "delete_content_item");
596
642
  const workspaceId = await resolveWorkspace(ctx, flags);
597
643
  return callTool(ctx, "delete_content_item", { workspaceId, taskId, contentItemId });
598
644
  }
599
645
 
600
646
  case "task clear-content": {
601
- const taskId = requirePositional(rest, "taskId", "ait task clear-content <taskId>");
647
+ const taskId = requirePositional(rest, "taskId", "ait task clear-content <taskId> --yes");
648
+ requireConfirmation(flags, "clear_task_content");
602
649
  const workspaceId = await resolveWorkspace(ctx, flags);
603
650
  return callTool(ctx, "clear_task_content", { workspaceId, taskId });
604
651
  }
@@ -634,8 +681,79 @@ export async function runCommand(argv, opts = {}) {
634
681
  });
635
682
  }
636
683
 
684
+ case "files move":
685
+ case "files copy": {
686
+ const usage = `ait files ${action} <fileId> [--folder <folderId>]`;
687
+ const fileId = requirePositional(rest, "fileId", usage);
688
+ const workspaceId = await resolveWorkspace(ctx, flags);
689
+ // Omitting --folder targets the workspace root, matching the tools'
690
+ // own default — for `move` that relocates the file, it isn't a no-op.
691
+ return callTool(ctx, action === "move" ? "move_file" : "copy_file", {
692
+ workspaceId,
693
+ fileId,
694
+ folderId: flags.folder ?? null,
695
+ });
696
+ }
697
+
698
+ case "folders list": {
699
+ const workspaceId = await resolveWorkspace(ctx, flags);
700
+ return callTool(ctx, "list_folders", {
701
+ workspaceId,
702
+ ...(flags.parent ? { parentId: flags.parent } : {}),
703
+ ...(flags.root === true ? { root: true } : {}),
704
+ });
705
+ }
706
+
707
+ case "folders create": {
708
+ if (!flags.name) throw new CliError("--name is required for folders create");
709
+ const workspaceId = await resolveWorkspace(ctx, flags);
710
+ return callTool(ctx, "create_folder", {
711
+ workspaceId,
712
+ name: flags.name,
713
+ ...(flags.parent ? { parentId: flags.parent } : {}),
714
+ });
715
+ }
716
+
717
+ case "folders rename": {
718
+ const folderId = requirePositional(rest, "folderId", "ait folders rename <folderId> --name <n>");
719
+ if (!flags.name) throw new CliError("--name is required for folders rename");
720
+ const workspaceId = await resolveWorkspace(ctx, flags);
721
+ return callTool(ctx, "rename_folder", { workspaceId, folderId, name: flags.name });
722
+ }
723
+
724
+ case "folders delete": {
725
+ const folderId = requirePositional(rest, "folderId", "ait folders delete <folderId> --yes");
726
+ requireConfirmation(flags, "delete_folder");
727
+ const workspaceId = await resolveWorkspace(ctx, flags);
728
+ return callTool(ctx, "delete_folder", { workspaceId, folderId });
729
+ }
730
+
731
+ case "integrations": {
732
+ const workspaceId = await resolveWorkspace(ctx, flags);
733
+ return callTool(ctx, "list_integrations", { workspaceId });
734
+ }
735
+
736
+ case "limits": {
737
+ const workspaceId = await resolveWorkspace(ctx, flags);
738
+ return callTool(ctx, "get_platform_limit", {
739
+ workspaceId,
740
+ ...(flags.platform !== undefined ? { platform: flags.platform } : {}),
741
+ ...(flags.text !== undefined ? { text: flags.text } : {}),
742
+ });
743
+ }
744
+
745
+ case "shorten": {
746
+ const url = requirePositional([action, ...rest].filter(Boolean), "url", "ait shorten <url> [--title <t>]");
747
+ const workspaceId = await resolveWorkspace(ctx, flags);
748
+ return callTool(ctx, "shorten_url", {
749
+ workspaceId,
750
+ url,
751
+ ...(flags.title ? { title: flags.title } : {}),
752
+ });
753
+ }
754
+
637
755
  default:
638
- throw new CliError(`Unknown command "${command}". Run "ait help" for usage, or "ait tools" to list MCP tools for "ait call".`);
756
+ throw new CliError(`Unknown command "${command}". Run "ait help" for the full command list.`);
639
757
  }
640
758
  }
641
759
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automate-it",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for Automate It — AI agents create content tasks, submit them through a human review gate, and publish everywhere. Speaks the Automate It MCP server; zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {