automate-it 0.2.0 → 0.3.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 +148 -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,52 @@ 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 x-limit [--text <draft>] X's limit for the connected account
129
+ (280 or 25000). With --text, returns the
130
+ draft's weighted length and whether it
131
+ fits — a URL counts 23, emoji 2.
132
+ ait shorten <url> [--title <t>] Shorten a link for a post
104
133
 
105
134
  All workspace commands accept --workspace <id>.`;
106
135
 
107
136
  export class CliError extends Error {}
108
137
 
109
- const BOOLEAN_FLAGS = new Set(["no-review", "claim", "mine", "clear", "auto", "help"]);
138
+ const BOOLEAN_FLAGS = new Set(["no-review", "claim", "mine", "clear", "auto", "help", "yes", "root"]);
139
+
140
+ // MCP tools that permanently remove data, keyed to what they destroy. Keyed by
141
+ // tool rather than by command so the gate lives next to the thing that does
142
+ // the damage — a second command routing to one of these inherits it.
143
+ const DESTRUCTIVE_TOOLS = new Map([
144
+ ["delete_task", "delete the task and everything attached to it"],
145
+ ["delete_content_item", "delete the content item"],
146
+ ["clear_task_content", "remove every content item from the task"],
147
+ ["delete_automation", "delete the automation and every task it ever spawned, published ones included"],
148
+ ["delete_folder", "delete the folder and every file and folder inside it"],
149
+ ]);
150
+
151
+ /** Destructive tools need an explicit --yes; nothing else is affected. */
152
+ function requireConfirmation(flags, toolName) {
153
+ const effect = DESTRUCTIVE_TOOLS.get(toolName);
154
+ if (!effect || flags.yes === true) return;
155
+ throw new CliError(
156
+ `Refusing to run without --yes: this would ${effect}. It cannot be undone — confirm with the operator, then re-run with --yes.`
157
+ );
158
+ }
110
159
 
111
160
  export function parseArgs(argv) {
112
161
  const positional = [];
@@ -342,28 +391,19 @@ export async function runCommand(argv, opts = {}) {
342
391
  return USAGE;
343
392
  }
344
393
 
345
- const SINGLE_WORD_COMMANDS = new Set(["tools", "call", "workspaces", "whoami", "upload-url"]);
394
+ const SINGLE_WORD_COMMANDS = new Set([
395
+ "workspaces",
396
+ "whoami",
397
+ "upload-url",
398
+ "integrations",
399
+ "x-limit",
400
+ "shorten",
401
+ ]);
346
402
  const command = SINGLE_WORD_COMMANDS.has(resource)
347
403
  ? resource
348
404
  : [resource, action].filter(Boolean).join(" ");
349
405
 
350
406
  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
407
  case "workspaces":
368
408
  return callTool(ctx, "list_workspaces", {});
369
409
 
@@ -448,6 +488,7 @@ export async function runCommand(argv, opts = {}) {
448
488
  delete: "delete_task",
449
489
  };
450
490
  const taskId = requirePositional(rest, "taskId", `ait task ${action} <taskId>`);
491
+ requireConfirmation(flags, toolByAction[action]);
451
492
  const workspaceId = await resolveWorkspace(ctx, flags);
452
493
  return callTool(ctx, toolByAction[action], { workspaceId, taskId });
453
494
  }
@@ -536,8 +577,9 @@ export async function runCommand(argv, opts = {}) {
536
577
  case "automation get":
537
578
  case "automation delete": {
538
579
  const automationId = requirePositional(rest, "automationId", `ait automation ${action} <automationId>`);
539
- const workspaceId = await resolveWorkspace(ctx, flags);
540
580
  const tool = action === "get" ? "get_automation" : "delete_automation";
581
+ requireConfirmation(flags, tool);
582
+ const workspaceId = await resolveWorkspace(ctx, flags);
541
583
  return callTool(ctx, tool, { workspaceId, automationId });
542
584
  }
543
585
 
@@ -590,15 +632,17 @@ export async function runCommand(argv, opts = {}) {
590
632
  }
591
633
 
592
634
  case "task delete-content": {
593
- const usage = "ait task delete-content <taskId> <contentItemId>";
635
+ const usage = "ait task delete-content <taskId> <contentItemId> --yes";
594
636
  const taskId = requirePositionalAt(rest, 0, "taskId", usage);
595
637
  const contentItemId = requirePositionalAt(rest, 1, "contentItemId", usage);
638
+ requireConfirmation(flags, "delete_content_item");
596
639
  const workspaceId = await resolveWorkspace(ctx, flags);
597
640
  return callTool(ctx, "delete_content_item", { workspaceId, taskId, contentItemId });
598
641
  }
599
642
 
600
643
  case "task clear-content": {
601
- const taskId = requirePositional(rest, "taskId", "ait task clear-content <taskId>");
644
+ const taskId = requirePositional(rest, "taskId", "ait task clear-content <taskId> --yes");
645
+ requireConfirmation(flags, "clear_task_content");
602
646
  const workspaceId = await resolveWorkspace(ctx, flags);
603
647
  return callTool(ctx, "clear_task_content", { workspaceId, taskId });
604
648
  }
@@ -634,8 +678,78 @@ export async function runCommand(argv, opts = {}) {
634
678
  });
635
679
  }
636
680
 
681
+ case "files move":
682
+ case "files copy": {
683
+ const usage = `ait files ${action} <fileId> [--folder <folderId>]`;
684
+ const fileId = requirePositional(rest, "fileId", usage);
685
+ const workspaceId = await resolveWorkspace(ctx, flags);
686
+ // Omitting --folder targets the workspace root, matching the tools'
687
+ // own default — for `move` that relocates the file, it isn't a no-op.
688
+ return callTool(ctx, action === "move" ? "move_file" : "copy_file", {
689
+ workspaceId,
690
+ fileId,
691
+ folderId: flags.folder ?? null,
692
+ });
693
+ }
694
+
695
+ case "folders list": {
696
+ const workspaceId = await resolveWorkspace(ctx, flags);
697
+ return callTool(ctx, "list_folders", {
698
+ workspaceId,
699
+ ...(flags.parent ? { parentId: flags.parent } : {}),
700
+ ...(flags.root === true ? { root: true } : {}),
701
+ });
702
+ }
703
+
704
+ case "folders create": {
705
+ if (!flags.name) throw new CliError("--name is required for folders create");
706
+ const workspaceId = await resolveWorkspace(ctx, flags);
707
+ return callTool(ctx, "create_folder", {
708
+ workspaceId,
709
+ name: flags.name,
710
+ ...(flags.parent ? { parentId: flags.parent } : {}),
711
+ });
712
+ }
713
+
714
+ case "folders rename": {
715
+ const folderId = requirePositional(rest, "folderId", "ait folders rename <folderId> --name <n>");
716
+ if (!flags.name) throw new CliError("--name is required for folders rename");
717
+ const workspaceId = await resolveWorkspace(ctx, flags);
718
+ return callTool(ctx, "rename_folder", { workspaceId, folderId, name: flags.name });
719
+ }
720
+
721
+ case "folders delete": {
722
+ const folderId = requirePositional(rest, "folderId", "ait folders delete <folderId> --yes");
723
+ requireConfirmation(flags, "delete_folder");
724
+ const workspaceId = await resolveWorkspace(ctx, flags);
725
+ return callTool(ctx, "delete_folder", { workspaceId, folderId });
726
+ }
727
+
728
+ case "integrations": {
729
+ const workspaceId = await resolveWorkspace(ctx, flags);
730
+ return callTool(ctx, "list_integrations", { workspaceId });
731
+ }
732
+
733
+ case "x-limit": {
734
+ const workspaceId = await resolveWorkspace(ctx, flags);
735
+ return callTool(ctx, "get_x_character_limit", {
736
+ workspaceId,
737
+ ...(flags.text !== undefined ? { text: flags.text } : {}),
738
+ });
739
+ }
740
+
741
+ case "shorten": {
742
+ const url = requirePositional([action, ...rest].filter(Boolean), "url", "ait shorten <url> [--title <t>]");
743
+ const workspaceId = await resolveWorkspace(ctx, flags);
744
+ return callTool(ctx, "shorten_url", {
745
+ workspaceId,
746
+ url,
747
+ ...(flags.title ? { title: flags.title } : {}),
748
+ });
749
+ }
750
+
637
751
  default:
638
- throw new CliError(`Unknown command "${command}". Run "ait help" for usage, or "ait tools" to list MCP tools for "ait call".`);
752
+ throw new CliError(`Unknown command "${command}". Run "ait help" for the full command list.`);
639
753
  }
640
754
  }
641
755
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automate-it",
3
- "version": "0.2.0",
3
+ "version": "0.3.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": {