@sulala/agent-os 0.1.23 → 0.1.24
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/dashboard-dist/assets/index-CVI9FAmG.css +1 -0
- package/dashboard-dist/assets/index-DdMu_Z6v.js +75 -0
- package/dashboard-dist/index.html +2 -2
- package/data/agents/crm_hubspot_agent.json +11 -0
- package/data/agents/research_agent.json +1 -1
- package/data/agents/social_media_agent.json +1 -1
- package/data/agents/source_verify_agent.json +10 -0
- package/data/agents/writer_agent.json +1 -1
- package/data/skills/content-writing/SKILL.md +32 -0
- package/dist/cli.js +123 -46
- package/dist/index.js +113 -35
- package/package.json +1 -1
- package/data/skills/gmail/SKILL.md +0 -55
- package/data/skills/gmail/references/send-email.md +0 -54
- package/data/skills/gmail/scripts/send_email.py +0 -94
- package/data/skills/youtube/SKILL.md +0 -91
- package/data/skills/youtube/config.schema.json +0 -11
- package/data/skills/youtube/package.json +0 -8
- package/data/skills/youtube/references/youtube-upload.md +0 -65
- package/data/skills/youtube/requirements.txt +0 -3
- package/data/skills/youtube/scripts/youtube_upload.js +0 -200
- package/data/skills/youtube/scripts/youtube_upload.py +0 -125
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Upload a video to YouTube. Uses OAuth credentials from YOUTUBE_CLIENT_SECRET_JSON
|
|
4
|
-
* (Skills config) or scripts/client_secret.json. Saves token.json in scripts/ for reuse.
|
|
5
|
-
*
|
|
6
|
-
* Usage: node scripts/youtube_upload.js --file VIDEO.mp4 --title "Title" [--description "..." ] [--tags "a,b"] [--privacy public|private|unlisted]
|
|
7
|
-
*
|
|
8
|
-
* Dependencies: npm install (in the skill directory). Agent can run: exec with skill_id "youtube", command "npm install".
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require("fs");
|
|
12
|
-
const path = require("path");
|
|
13
|
-
const http = require("http");
|
|
14
|
-
|
|
15
|
-
const SCRIPT_DIR = path.resolve(path.dirname(__filename));
|
|
16
|
-
const SKILL_DIR = path.resolve(SCRIPT_DIR, "..");
|
|
17
|
-
const TOKEN_PATH = path.join(SCRIPT_DIR, "token.json");
|
|
18
|
-
const SCOPES = ["https://www.googleapis.com/auth/youtube.upload"];
|
|
19
|
-
const REDIRECT_URI = "http://localhost:8090/";
|
|
20
|
-
|
|
21
|
-
function parseArgs() {
|
|
22
|
-
const args = process.argv.slice(2);
|
|
23
|
-
const out = { file: null, title: null, description: "", tags: "", privacy: "public" };
|
|
24
|
-
for (let i = 0; i < args.length; i++) {
|
|
25
|
-
if (args[i] === "--file" && args[i + 1]) out.file = args[++i];
|
|
26
|
-
else if (args[i] === "--title" && args[i + 1]) out.title = args[++i];
|
|
27
|
-
else if (args[i] === "--description" && args[i + 1]) out.description = args[++i];
|
|
28
|
-
else if (args[i] === "--tags" && args[i + 1]) out.tags = args[++i];
|
|
29
|
-
else if (args[i] === "--privacy" && args[i + 1]) out.privacy = args[++i];
|
|
30
|
-
}
|
|
31
|
-
return out;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getClientSecret() {
|
|
35
|
-
const fromEnv = process.env.YOUTUBE_CLIENT_SECRET_JSON;
|
|
36
|
-
if (fromEnv && fromEnv.trim()) {
|
|
37
|
-
try {
|
|
38
|
-
return JSON.parse(fromEnv.trim());
|
|
39
|
-
} catch (e) {
|
|
40
|
-
console.error("ERROR: YOUTUBE_CLIENT_SECRET_JSON is not valid JSON.", e.message);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const p = path.join(SCRIPT_DIR, "client_secret.json");
|
|
45
|
-
if (!fs.existsSync(p)) {
|
|
46
|
-
console.error("ERROR: client_secret.json not found and YOUTUBE_CLIENT_SECRET_JSON not set.");
|
|
47
|
-
console.error("Add via Skills → YouTube → Configure or place scripts/client_secret.json.");
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getOAuth2Client(credentials) {
|
|
54
|
-
const { google } = require("googleapis");
|
|
55
|
-
const client = credentials.installed || credentials.web;
|
|
56
|
-
if (!client) {
|
|
57
|
-
console.error("ERROR: client_secret JSON must have 'installed' or 'web' with client_id and client_secret.");
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
return new google.auth.OAuth2(client.client_id, client.client_secret, REDIRECT_URI);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function loadSavedToken() {
|
|
64
|
-
if (!fs.existsSync(TOKEN_PATH)) return null;
|
|
65
|
-
try {
|
|
66
|
-
return JSON.parse(fs.readFileSync(TOKEN_PATH, "utf8"));
|
|
67
|
-
} catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function saveToken(tokens) {
|
|
73
|
-
fs.writeFileSync(TOKEN_PATH, JSON.stringify(tokens, null, 2), "utf8");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function authorize(oauth2Client) {
|
|
77
|
-
return new Promise((resolve, reject) => {
|
|
78
|
-
const authUrl = oauth2Client.generateAuthUrl({
|
|
79
|
-
access_type: "offline",
|
|
80
|
-
scope: SCOPES,
|
|
81
|
-
prompt: "consent",
|
|
82
|
-
});
|
|
83
|
-
const server = http
|
|
84
|
-
.createServer(async (req, res) => {
|
|
85
|
-
const url = new URL(req.url || "", `http://localhost`);
|
|
86
|
-
const code = url.searchParams.get("code");
|
|
87
|
-
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
88
|
-
if (code) {
|
|
89
|
-
res.end("Authorized. You can close this tab.");
|
|
90
|
-
server.close();
|
|
91
|
-
try {
|
|
92
|
-
const { tokens } = await oauth2Client.getToken(code);
|
|
93
|
-
oauth2Client.setCredentials(tokens);
|
|
94
|
-
saveToken(tokens);
|
|
95
|
-
resolve(oauth2Client);
|
|
96
|
-
} catch (e) {
|
|
97
|
-
reject(e);
|
|
98
|
-
}
|
|
99
|
-
} else {
|
|
100
|
-
res.end("Missing code. Try again.");
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
.listen(8090, "localhost", () => {
|
|
104
|
-
console.error("Open this URL in your browser to authorize:");
|
|
105
|
-
console.error(authUrl);
|
|
106
|
-
const op = require("child_process").spawn(
|
|
107
|
-
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open",
|
|
108
|
-
[authUrl],
|
|
109
|
-
{ stdio: "ignore" }
|
|
110
|
-
);
|
|
111
|
-
op.on("error", () => {});
|
|
112
|
-
});
|
|
113
|
-
server.on("error", reject);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function getAuthenticatedClient() {
|
|
118
|
-
let credentials;
|
|
119
|
-
try {
|
|
120
|
-
credentials = getClientSecret();
|
|
121
|
-
} catch (e) {
|
|
122
|
-
console.error("ERROR:", e.message);
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
const oauth2Client = getOAuth2Client(credentials);
|
|
126
|
-
const token = loadSavedToken();
|
|
127
|
-
if (token) {
|
|
128
|
-
oauth2Client.setCredentials(token);
|
|
129
|
-
oauth2Client.on("tokens", (tokens) => {
|
|
130
|
-
const creds = oauth2Client.credentials;
|
|
131
|
-
if (tokens.refresh_token) creds.refresh_token = tokens.refresh_token;
|
|
132
|
-
saveToken(creds);
|
|
133
|
-
});
|
|
134
|
-
return oauth2Client;
|
|
135
|
-
}
|
|
136
|
-
return authorize(oauth2Client);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function uploadVideo(oauth2Client, filePath, title, description, tagsList, privacy) {
|
|
140
|
-
const { google } = require("googleapis");
|
|
141
|
-
const youtube = google.youtube({ version: "v3", auth: oauth2Client });
|
|
142
|
-
const body = {
|
|
143
|
-
snippet: {
|
|
144
|
-
title,
|
|
145
|
-
description: description || "",
|
|
146
|
-
tags: tagsList,
|
|
147
|
-
categoryId: "22",
|
|
148
|
-
},
|
|
149
|
-
status: { privacyStatus: privacy },
|
|
150
|
-
};
|
|
151
|
-
const media = {
|
|
152
|
-
body: fs.createReadStream(filePath),
|
|
153
|
-
};
|
|
154
|
-
const res = await youtube.videos.insert({
|
|
155
|
-
part: "snippet,status",
|
|
156
|
-
requestBody: body,
|
|
157
|
-
media,
|
|
158
|
-
});
|
|
159
|
-
const videoId = res.data.id;
|
|
160
|
-
console.log("Uploaded: https://youtube.com/watch?v=" + videoId);
|
|
161
|
-
return videoId;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
async function main() {
|
|
165
|
-
const args = parseArgs();
|
|
166
|
-
if (!args.file || !args.title) {
|
|
167
|
-
console.error("Usage: node youtube_upload.js --file <path> --title \"Title\" [--description \"...\"] [--tags \"a,b\"] [--privacy public|private|unlisted]");
|
|
168
|
-
process.exit(1);
|
|
169
|
-
}
|
|
170
|
-
if (!["public", "private", "unlisted"].includes(args.privacy)) {
|
|
171
|
-
console.error("ERROR: --privacy must be public, private, or unlisted");
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
let filePath = path.resolve(args.file);
|
|
175
|
-
if (!path.isAbsolute(args.file)) filePath = path.resolve(SCRIPT_DIR, args.file);
|
|
176
|
-
if (!fs.existsSync(filePath)) {
|
|
177
|
-
console.error("ERROR: File not found:", filePath);
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
const tagsList = args.tags ? args.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
const auth = await getAuthenticatedClient();
|
|
184
|
-
await uploadVideo(auth, filePath, args.title, args.description, tagsList, args.privacy);
|
|
185
|
-
const creds = auth.credentials;
|
|
186
|
-
if (creds && (creds.access_token || creds.refresh_token)) saveToken(creds);
|
|
187
|
-
process.exit(0);
|
|
188
|
-
} catch (e) {
|
|
189
|
-
if (e.code === "MODULE_NOT_FOUND" && (e.message || "").includes("googleapis")) {
|
|
190
|
-
console.error("ERROR: Missing npm dependencies. Install with:");
|
|
191
|
-
console.error(" npm install");
|
|
192
|
-
console.error("Run that from the skill directory (exec with skill_id 'youtube', command 'npm install'), then retry.");
|
|
193
|
-
process.exit(1);
|
|
194
|
-
}
|
|
195
|
-
console.error("ERROR:", e.message || e);
|
|
196
|
-
process.exit(1);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
main();
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Upload a video to YouTube using OAuth credentials in the skill directory.
|
|
3
|
-
|
|
4
|
-
Usage:
|
|
5
|
-
python3 youtube_upload.py --file VIDEO.mp4 --title "Title" --description "Description" [--tags "a,b,c"] [--privacy public|private|unlisted]
|
|
6
|
-
|
|
7
|
-
First run: place client_secret.json (from Google Cloud Console) in this script's directory.
|
|
8
|
-
Browser OAuth will run once; token.json is saved for reuse.
|
|
9
|
-
|
|
10
|
-
Requires: pip install google-api-python-client google-auth-oauthlib google-auth-httplib2
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import argparse
|
|
14
|
-
import os
|
|
15
|
-
import sys
|
|
16
|
-
import tempfile
|
|
17
|
-
from pathlib import Path
|
|
18
|
-
|
|
19
|
-
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
20
|
-
SKILL_DIR = SCRIPT_DIR.parent
|
|
21
|
-
TOKEN_FILE = SCRIPT_DIR / "token.json"
|
|
22
|
-
SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]
|
|
23
|
-
|
|
24
|
-
REQUIREMENTS_MSG = (
|
|
25
|
-
"YouTube skill requires Google API packages. Install them with:\n"
|
|
26
|
-
f" pip install -r \"{SKILL_DIR / 'requirements.txt'}\"\n"
|
|
27
|
-
"Or: pip install google-api-python-client google-auth-oauthlib google-auth-httplib2"
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _client_secret_path() -> Path:
|
|
32
|
-
"""Path to client secrets: from env YOUTUBE_CLIENT_SECRET_JSON (Skills UI) or scripts/client_secret.json."""
|
|
33
|
-
raw = os.environ.get("YOUTUBE_CLIENT_SECRET_JSON", "").strip()
|
|
34
|
-
if raw:
|
|
35
|
-
try:
|
|
36
|
-
f = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
|
|
37
|
-
f.write(raw)
|
|
38
|
-
f.close()
|
|
39
|
-
return Path(f.name)
|
|
40
|
-
except Exception:
|
|
41
|
-
pass
|
|
42
|
-
return SCRIPT_DIR / "client_secret.json"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def get_authenticated_service():
|
|
46
|
-
try:
|
|
47
|
-
from google.auth.transport.requests import Request
|
|
48
|
-
from google.oauth2.credentials import Credentials
|
|
49
|
-
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
50
|
-
from googleapiclient.discovery import build
|
|
51
|
-
except ModuleNotFoundError:
|
|
52
|
-
print("ERROR: Missing required Google API packages.", file=sys.stderr)
|
|
53
|
-
print(REQUIREMENTS_MSG, file=sys.stderr)
|
|
54
|
-
sys.exit(1)
|
|
55
|
-
|
|
56
|
-
client_secret_file = _client_secret_path()
|
|
57
|
-
creds = None
|
|
58
|
-
if TOKEN_FILE.exists():
|
|
59
|
-
creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)
|
|
60
|
-
if not creds or not creds.valid:
|
|
61
|
-
if creds and creds.expired and creds.refresh_token:
|
|
62
|
-
creds.refresh(Request())
|
|
63
|
-
else:
|
|
64
|
-
if not client_secret_file.exists():
|
|
65
|
-
print("ERROR: client_secret.json not found and YOUTUBE_CLIENT_SECRET_JSON not set.", file=sys.stderr)
|
|
66
|
-
print("Add via Skills → YouTube → Configure (paste OAuth client JSON) or place scripts/client_secret.json.", file=sys.stderr)
|
|
67
|
-
sys.exit(1)
|
|
68
|
-
flow = InstalledAppFlow.from_client_secrets_file(str(client_secret_file), SCOPES)
|
|
69
|
-
creds = flow.run_local_server(port=8090, open_browser=True)
|
|
70
|
-
if client_secret_file != SCRIPT_DIR / "client_secret.json":
|
|
71
|
-
try:
|
|
72
|
-
client_secret_file.unlink()
|
|
73
|
-
except Exception:
|
|
74
|
-
pass
|
|
75
|
-
TOKEN_FILE.write_text(creds.to_json())
|
|
76
|
-
return build("youtube", "v3", credentials=creds)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def upload(youtube, file_path: str, title: str, description: str, tags: list[str], privacy: str, category_id: str = "22"):
|
|
80
|
-
from googleapiclient.http import MediaFileUpload
|
|
81
|
-
|
|
82
|
-
body = {
|
|
83
|
-
"snippet": {
|
|
84
|
-
"title": title,
|
|
85
|
-
"description": description,
|
|
86
|
-
"tags": tags,
|
|
87
|
-
"categoryId": category_id,
|
|
88
|
-
},
|
|
89
|
-
"status": {"privacyStatus": privacy},
|
|
90
|
-
}
|
|
91
|
-
media = MediaFileUpload(file_path, chunksize=1024 * 1024, resumable=True)
|
|
92
|
-
request = youtube.videos().insert(part="snippet,status", body=body, media_body=media)
|
|
93
|
-
response = None
|
|
94
|
-
while response is None:
|
|
95
|
-
status, response = request.next_chunk()
|
|
96
|
-
if status:
|
|
97
|
-
print(f" Progress: {int(status.progress() * 100)}%")
|
|
98
|
-
video_id = response["id"]
|
|
99
|
-
print(f"Uploaded: https://youtube.com/watch?v={video_id}")
|
|
100
|
-
return video_id
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def main():
|
|
104
|
-
ap = argparse.ArgumentParser(description="Upload a video to YouTube.")
|
|
105
|
-
ap.add_argument("--file", required=True, help="Path to video file (e.g. video.mp4)")
|
|
106
|
-
ap.add_argument("--title", required=True, help="Video title")
|
|
107
|
-
ap.add_argument("--description", default="", help="Video description")
|
|
108
|
-
ap.add_argument("--tags", default="", help="Comma-separated tags (e.g. shorts,demo)")
|
|
109
|
-
ap.add_argument("--privacy", default="public", choices=("public", "private", "unlisted"), help="Privacy status")
|
|
110
|
-
args = ap.parse_args()
|
|
111
|
-
|
|
112
|
-
file_path = Path(args.file)
|
|
113
|
-
if not file_path.is_absolute():
|
|
114
|
-
file_path = SCRIPT_DIR / file_path
|
|
115
|
-
if not file_path.exists():
|
|
116
|
-
print(f"ERROR: File not found: {file_path}", file=sys.stderr)
|
|
117
|
-
sys.exit(1)
|
|
118
|
-
|
|
119
|
-
tags_list = [t.strip() for t in args.tags.split(",") if t.strip()]
|
|
120
|
-
youtube = get_authenticated_service()
|
|
121
|
-
upload(youtube, str(file_path), args.title, args.description, tags_list, args.privacy)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if __name__ == "__main__":
|
|
125
|
-
main()
|