automate-it 0.1.1 → 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.
- package/ait.mjs +179 -40
- 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_*)
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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,17 +31,21 @@ 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
|
|
38
45
|
[--instructions <text>] [--output-types x,linkedin,...]
|
|
39
46
|
[--publish-mode manual|immediate|scheduled] [--publish-at <ISO date>]
|
|
47
|
+
Omitted --publish-mode falls back to the workspace's default publish
|
|
48
|
+
mode; scheduled without --publish-at auto-schedules at approval.
|
|
40
49
|
[--no-review] [--claim] [--skills <names or ids>] [--assign <userId|me>]
|
|
41
50
|
--claim creates the task already claimed (status "working") so the
|
|
42
51
|
built-in worker never picks it up — use it when YOU will generate the
|
|
@@ -62,8 +71,9 @@ Tasks (the review-gate loop):
|
|
|
62
71
|
[--media '<json array>'] [--sort-order <n>]
|
|
63
72
|
ait task update-content <taskId> <contentItemId>
|
|
64
73
|
[--body <text>] [--title <t>] Rewrite a content item in place
|
|
65
|
-
ait task delete-content <taskId> <contentItemId>
|
|
66
|
-
ait task clear-content <taskId>
|
|
74
|
+
ait task delete-content <taskId> <contentItemId> --yes
|
|
75
|
+
ait task clear-content <taskId> --yes
|
|
76
|
+
Remove every content item from a task
|
|
67
77
|
ait task complete <taskId> Finish work (status → review/approved)
|
|
68
78
|
ait task comment <taskId> --comment <text>
|
|
69
79
|
Leave a note for the human reviewer
|
|
@@ -71,7 +81,12 @@ Tasks (the review-gate loop):
|
|
|
71
81
|
ait task approve <taskId> Approve (reviewer/admin keys)
|
|
72
82
|
ait task reject <taskId> [--comment <text>]
|
|
73
83
|
ait task publish <taskId> [--platforms a,b]
|
|
74
|
-
ait task
|
|
84
|
+
ait task schedule <taskId> --at <ISO date> | --auto | --clear
|
|
85
|
+
Schedule an approved task to publish
|
|
86
|
+
later; --auto picks the next open slot
|
|
87
|
+
per the workspace's scheduling rules;
|
|
88
|
+
--clear returns it to manual
|
|
89
|
+
ait task delete <taskId> --yes
|
|
75
90
|
|
|
76
91
|
Skills (workspace voice, formatting rules, bundled reference files):
|
|
77
92
|
ait skills list id, name, description for each skill
|
|
@@ -84,7 +99,9 @@ Automations:
|
|
|
84
99
|
ait automation get <automationId>
|
|
85
100
|
ait automation update <automationId> [--name <n>] [--instructions <text>]
|
|
86
101
|
[--output-types x,...] [--schedule <json>]
|
|
87
|
-
ait automation delete <automationId>
|
|
102
|
+
ait automation delete <automationId> --yes
|
|
103
|
+
Also deletes every task it spawned,
|
|
104
|
+
published ones included
|
|
88
105
|
ait automation run <automationId> Trigger now; returns the spawned task
|
|
89
106
|
|
|
90
107
|
Files:
|
|
@@ -93,13 +110,52 @@ Files:
|
|
|
93
110
|
ait files list [--search <s>] [--mime-type <m>] [--limit <n>]
|
|
94
111
|
ait files download-url <fileId> [--expires-in <seconds>]
|
|
95
112
|
Presigned URL — download it yourself
|
|
96
|
-
|
|
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
|
|
97
133
|
|
|
98
134
|
All workspace commands accept --workspace <id>.`;
|
|
99
135
|
|
|
100
136
|
export class CliError extends Error {}
|
|
101
137
|
|
|
102
|
-
const BOOLEAN_FLAGS = new Set(["no-review", "claim", "mine", "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
|
+
}
|
|
103
159
|
|
|
104
160
|
export function parseArgs(argv) {
|
|
105
161
|
const positional = [];
|
|
@@ -268,18 +324,15 @@ export function shapeLinks(task) {
|
|
|
268
324
|
/** Fields shared by `task create` and `task submit`. */
|
|
269
325
|
function buildCreateTaskArgs(flags) {
|
|
270
326
|
if (!flags.title) throw new CliError("--title is required for task create");
|
|
271
|
-
const publishMode = flags["publish-mode"]
|
|
272
|
-
if (!PUBLISH_MODES.includes(publishMode)) {
|
|
327
|
+
const publishMode = flags["publish-mode"];
|
|
328
|
+
if (publishMode && !PUBLISH_MODES.includes(publishMode)) {
|
|
273
329
|
throw new CliError(`--publish-mode must be one of: ${PUBLISH_MODES.join(", ")}`);
|
|
274
330
|
}
|
|
275
|
-
if (publishMode === "scheduled" && !flags["publish-at"]) {
|
|
276
|
-
throw new CliError('--publish-at is required when --publish-mode is "scheduled"');
|
|
277
|
-
}
|
|
278
331
|
return {
|
|
279
332
|
title: flags.title,
|
|
280
333
|
...(flags.instructions ? { instructions: flags.instructions } : {}),
|
|
281
334
|
outputTypes: csv(flags["output-types"]),
|
|
282
|
-
publishMode,
|
|
335
|
+
...(publishMode ? { publishMode } : {}),
|
|
283
336
|
...(flags["publish-at"] ? { publishAt: flags["publish-at"] } : {}),
|
|
284
337
|
requiresReview: flags["no-review"] !== true,
|
|
285
338
|
};
|
|
@@ -338,28 +391,19 @@ export async function runCommand(argv, opts = {}) {
|
|
|
338
391
|
return USAGE;
|
|
339
392
|
}
|
|
340
393
|
|
|
341
|
-
const SINGLE_WORD_COMMANDS = new Set([
|
|
394
|
+
const SINGLE_WORD_COMMANDS = new Set([
|
|
395
|
+
"workspaces",
|
|
396
|
+
"whoami",
|
|
397
|
+
"upload-url",
|
|
398
|
+
"integrations",
|
|
399
|
+
"x-limit",
|
|
400
|
+
"shorten",
|
|
401
|
+
]);
|
|
342
402
|
const command = SINGLE_WORD_COMMANDS.has(resource)
|
|
343
403
|
? resource
|
|
344
404
|
: [resource, action].filter(Boolean).join(" ");
|
|
345
405
|
|
|
346
406
|
switch (command) {
|
|
347
|
-
case "tools": {
|
|
348
|
-
const result = await mcpRequest(ctx, "tools/list", {});
|
|
349
|
-
return (result?.tools ?? [])
|
|
350
|
-
.map((t) => `${t.name} — ${(t.description || "").split("\n")[0]}`)
|
|
351
|
-
.join("\n");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
case "call": {
|
|
355
|
-
const toolName = requirePositional([action, ...rest].filter(Boolean), "tool", "ait call <tool> [--args '<json>']");
|
|
356
|
-
const args = flags.args ? parseJsonFlag(flags.args, "args") : {};
|
|
357
|
-
if (args.workspaceId === undefined && toolName !== "list_workspaces" && toolName !== "get_clerk_user_data") {
|
|
358
|
-
args.workspaceId = await resolveWorkspace(ctx, flags);
|
|
359
|
-
}
|
|
360
|
-
return callTool(ctx, toolName, args);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
407
|
case "workspaces":
|
|
364
408
|
return callTool(ctx, "list_workspaces", {});
|
|
365
409
|
|
|
@@ -444,6 +488,7 @@ export async function runCommand(argv, opts = {}) {
|
|
|
444
488
|
delete: "delete_task",
|
|
445
489
|
};
|
|
446
490
|
const taskId = requirePositional(rest, "taskId", `ait task ${action} <taskId>`);
|
|
491
|
+
requireConfirmation(flags, toolByAction[action]);
|
|
447
492
|
const workspaceId = await resolveWorkspace(ctx, flags);
|
|
448
493
|
return callTool(ctx, toolByAction[action], { workspaceId, taskId });
|
|
449
494
|
}
|
|
@@ -468,6 +513,27 @@ export async function runCommand(argv, opts = {}) {
|
|
|
468
513
|
});
|
|
469
514
|
}
|
|
470
515
|
|
|
516
|
+
case "task schedule": {
|
|
517
|
+
const taskId = requirePositional(rest, "taskId", "ait task schedule <taskId> --at <ISO date> | --auto | --clear");
|
|
518
|
+
const modes = [flags.at ? "--at" : null, flags.auto === true ? "--auto" : null, flags.clear === true ? "--clear" : null].filter(Boolean);
|
|
519
|
+
if (modes.length > 1) {
|
|
520
|
+
throw new CliError(`Use only one of --at, --auto, --clear (got ${modes.join(" and ")})`);
|
|
521
|
+
}
|
|
522
|
+
let publishAt = null;
|
|
523
|
+
if (flags.auto === true) {
|
|
524
|
+
publishAt = "auto";
|
|
525
|
+
} else if (flags.clear !== true) {
|
|
526
|
+
if (!flags.at) {
|
|
527
|
+
throw new CliError("task schedule requires --at <ISO date>, --auto, or --clear");
|
|
528
|
+
}
|
|
529
|
+
const at = new Date(flags.at);
|
|
530
|
+
if (isNaN(at.getTime())) throw new CliError(`--at is not a valid date: ${flags.at}`);
|
|
531
|
+
publishAt = at.toISOString();
|
|
532
|
+
}
|
|
533
|
+
const workspaceId = await resolveWorkspace(ctx, flags);
|
|
534
|
+
return callTool(ctx, "schedule_task", { workspaceId, taskId, publishAt });
|
|
535
|
+
}
|
|
536
|
+
|
|
471
537
|
case "task add-content": {
|
|
472
538
|
const taskId = requirePositional(rest, "taskId", "ait task add-content <taskId> --body <text>");
|
|
473
539
|
if (!flags.body && !flags.media) {
|
|
@@ -511,8 +577,9 @@ export async function runCommand(argv, opts = {}) {
|
|
|
511
577
|
case "automation get":
|
|
512
578
|
case "automation delete": {
|
|
513
579
|
const automationId = requirePositional(rest, "automationId", `ait automation ${action} <automationId>`);
|
|
514
|
-
const workspaceId = await resolveWorkspace(ctx, flags);
|
|
515
580
|
const tool = action === "get" ? "get_automation" : "delete_automation";
|
|
581
|
+
requireConfirmation(flags, tool);
|
|
582
|
+
const workspaceId = await resolveWorkspace(ctx, flags);
|
|
516
583
|
return callTool(ctx, tool, { workspaceId, automationId });
|
|
517
584
|
}
|
|
518
585
|
|
|
@@ -565,15 +632,17 @@ export async function runCommand(argv, opts = {}) {
|
|
|
565
632
|
}
|
|
566
633
|
|
|
567
634
|
case "task delete-content": {
|
|
568
|
-
const usage = "ait task delete-content <taskId> <contentItemId>";
|
|
635
|
+
const usage = "ait task delete-content <taskId> <contentItemId> --yes";
|
|
569
636
|
const taskId = requirePositionalAt(rest, 0, "taskId", usage);
|
|
570
637
|
const contentItemId = requirePositionalAt(rest, 1, "contentItemId", usage);
|
|
638
|
+
requireConfirmation(flags, "delete_content_item");
|
|
571
639
|
const workspaceId = await resolveWorkspace(ctx, flags);
|
|
572
640
|
return callTool(ctx, "delete_content_item", { workspaceId, taskId, contentItemId });
|
|
573
641
|
}
|
|
574
642
|
|
|
575
643
|
case "task clear-content": {
|
|
576
|
-
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");
|
|
577
646
|
const workspaceId = await resolveWorkspace(ctx, flags);
|
|
578
647
|
return callTool(ctx, "clear_task_content", { workspaceId, taskId });
|
|
579
648
|
}
|
|
@@ -609,8 +678,78 @@ export async function runCommand(argv, opts = {}) {
|
|
|
609
678
|
});
|
|
610
679
|
}
|
|
611
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
|
+
|
|
612
751
|
default:
|
|
613
|
-
throw new CliError(`Unknown command "${command}". Run "ait help" for
|
|
752
|
+
throw new CliError(`Unknown command "${command}". Run "ait help" for the full command list.`);
|
|
614
753
|
}
|
|
615
754
|
}
|
|
616
755
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "automate-it",
|
|
3
|
-
"version": "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": {
|