viveworker 0.4.1 → 0.4.3
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/package.json +1 -1
- package/scripts/a2a-cli.mjs +171 -1
- package/scripts/a2a-handler.mjs +15 -7
- package/scripts/viveworker-bridge.mjs +40 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Local mobile companion for Codex Desktop and Claude Desktop — approvals, code review, Moltbook drafts, and A2A (Agent-to-Agent) task relay on your LAN.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/scripts/a2a-cli.mjs
CHANGED
|
@@ -33,8 +33,15 @@ export async function runA2ACli(args) {
|
|
|
33
33
|
switch (cmd) {
|
|
34
34
|
case "setup":
|
|
35
35
|
return handleSetup(args.slice(1));
|
|
36
|
+
case "activity":
|
|
37
|
+
return handleActivity(args.slice(1));
|
|
38
|
+
case "card":
|
|
39
|
+
return handleCard(args.slice(1));
|
|
36
40
|
default:
|
|
37
|
-
console.log("
|
|
41
|
+
console.log("Commands:");
|
|
42
|
+
console.log(" viveworker a2a setup --user-id <id> [--description <text>] [--skills <csv>]");
|
|
43
|
+
console.log(" viveworker a2a activity [--state-file <path>]");
|
|
44
|
+
console.log(" viveworker a2a card [--description <text>] [--skills <csv>] [--avatar <url-or-emoji>]");
|
|
38
45
|
if (cmd && cmd !== "help" && cmd !== "--help") {
|
|
39
46
|
throw new Error(`Unknown command: ${cmd}`);
|
|
40
47
|
}
|
|
@@ -52,6 +59,7 @@ async function handleSetup(args) {
|
|
|
52
59
|
const timeout = Number(flags["timeout"]) || DEFAULT_TIMEOUT;
|
|
53
60
|
const description = flags["description"] || "";
|
|
54
61
|
const skillsRaw = flags["skills"] || "";
|
|
62
|
+
const avatar = flags["avatar"] || "";
|
|
55
63
|
|
|
56
64
|
if (!userId) {
|
|
57
65
|
throw new Error("--user-id is required\nUsage: viveworker a2a setup --user-id <id>");
|
|
@@ -62,6 +70,7 @@ async function handleSetup(args) {
|
|
|
62
70
|
console.log(` User ID: ${userId}`);
|
|
63
71
|
if (description) console.log(` Desc: ${description}`);
|
|
64
72
|
if (skillsRaw) console.log(` Skills: ${skillsRaw}`);
|
|
73
|
+
if (avatar) console.log(` Avatar: ${avatar}`);
|
|
65
74
|
console.log();
|
|
66
75
|
|
|
67
76
|
// Step 1: Create setup session
|
|
@@ -135,6 +144,7 @@ async function handleSetup(args) {
|
|
|
135
144
|
};
|
|
136
145
|
if (description) envVars.A2A_DESCRIPTION = description;
|
|
137
146
|
if (skillsRaw) envVars.A2A_SKILLS = skillsRaw;
|
|
147
|
+
if (avatar) envVars.A2A_AVATAR = avatar;
|
|
138
148
|
|
|
139
149
|
const updated = upsertEnvText(currentEnv, envVars);
|
|
140
150
|
|
|
@@ -146,6 +156,166 @@ async function handleSetup(args) {
|
|
|
146
156
|
console.log(` Your A2A endpoint: ${result.relayUrl}/${result.userId}\n`);
|
|
147
157
|
}
|
|
148
158
|
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// activity command
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
async function handleActivity(args) {
|
|
164
|
+
const flags = parseFlags(args);
|
|
165
|
+
|
|
166
|
+
// Find state file: explicit flag > STATE_FILE env > default locations
|
|
167
|
+
const candidates = [
|
|
168
|
+
flags["state-file"] || flags["stateFile"],
|
|
169
|
+
process.env.STATE_FILE,
|
|
170
|
+
path.join(os.homedir(), ".viveworker", "state.json"),
|
|
171
|
+
path.join(process.cwd(), ".viveworker-state.json"),
|
|
172
|
+
].filter(Boolean);
|
|
173
|
+
|
|
174
|
+
let stateData = null;
|
|
175
|
+
let usedPath = "";
|
|
176
|
+
for (const candidate of candidates) {
|
|
177
|
+
try {
|
|
178
|
+
const raw = await fs.readFile(candidate, "utf8");
|
|
179
|
+
stateData = JSON.parse(raw);
|
|
180
|
+
usedPath = candidate;
|
|
181
|
+
break;
|
|
182
|
+
} catch {
|
|
183
|
+
// try next
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!stateData) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"Could not find viveworker state file.\n" +
|
|
190
|
+
"Try: viveworker a2a activity --state-file /path/to/.viveworker-state.json\n" +
|
|
191
|
+
"Or set STATE_FILE environment variable."
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const entries = stateData.recentTimelineEntries || [];
|
|
196
|
+
if (entries.length === 0) {
|
|
197
|
+
console.log(JSON.stringify({ totalEntries: 0, providers: {}, threads: [], recentTasks: [] }));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Provider usage
|
|
202
|
+
const providers = {};
|
|
203
|
+
for (const e of entries) {
|
|
204
|
+
if (e.provider) providers[e.provider] = (providers[e.provider] || 0) + 1;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Thread topics with activity counts
|
|
208
|
+
const threadMap = new Map();
|
|
209
|
+
for (const e of entries) {
|
|
210
|
+
const label = e.threadLabel || e.title;
|
|
211
|
+
if (!label || label === "Moltbook") continue;
|
|
212
|
+
if (!threadMap.has(label)) threadMap.set(label, { count: 0, providers: new Set() });
|
|
213
|
+
const t = threadMap.get(label);
|
|
214
|
+
t.count++;
|
|
215
|
+
if (e.provider) t.providers.add(e.provider);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const threads = [...threadMap.entries()]
|
|
219
|
+
.sort((a, b) => b[1].count - a[1].count)
|
|
220
|
+
.map(([label, v]) => ({
|
|
221
|
+
label: label.slice(0, 120),
|
|
222
|
+
count: v.count,
|
|
223
|
+
providers: [...v.providers],
|
|
224
|
+
}));
|
|
225
|
+
|
|
226
|
+
// Recent task titles (from assistant_final and user_message kinds)
|
|
227
|
+
const taskKinds = new Set(["assistant_final", "user_message", "completion"]);
|
|
228
|
+
const recentTasks = [];
|
|
229
|
+
const seenTitles = new Set();
|
|
230
|
+
for (let i = entries.length - 1; i >= 0 && recentTasks.length < 15; i--) {
|
|
231
|
+
const e = entries[i];
|
|
232
|
+
if (!taskKinds.has(e.kind)) continue;
|
|
233
|
+
const title = (e.title || e.threadLabel || "").trim();
|
|
234
|
+
if (!title || seenTitles.has(title)) continue;
|
|
235
|
+
seenTitles.add(title);
|
|
236
|
+
recentTasks.push({
|
|
237
|
+
title: title.slice(0, 120),
|
|
238
|
+
provider: e.provider || "",
|
|
239
|
+
kind: e.kind,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const result = {
|
|
244
|
+
stateFile: usedPath,
|
|
245
|
+
totalEntries: entries.length,
|
|
246
|
+
providers,
|
|
247
|
+
threads,
|
|
248
|
+
recentTasks,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
console.log(JSON.stringify(result, null, 2));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// card command
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
|
|
258
|
+
async function handleCard(args) {
|
|
259
|
+
const flags = parseFlags(args);
|
|
260
|
+
const description = flags["description"] || "";
|
|
261
|
+
const skillsRaw = flags["skills"] || "";
|
|
262
|
+
const avatar = flags["avatar"] || "";
|
|
263
|
+
|
|
264
|
+
if (!description && !skillsRaw && !avatar) {
|
|
265
|
+
// No flags — show current values
|
|
266
|
+
let currentEnv = "";
|
|
267
|
+
try {
|
|
268
|
+
currentEnv = await fs.readFile(A2A_ENV_FILE, "utf8");
|
|
269
|
+
} catch {
|
|
270
|
+
throw new Error(`No a2a.env found at ${A2A_ENV_FILE}. Run 'viveworker a2a setup' first.`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const currentDesc = envValue(currentEnv, "A2A_DESCRIPTION");
|
|
274
|
+
const currentSkills = envValue(currentEnv, "A2A_SKILLS");
|
|
275
|
+
const currentAvatar = envValue(currentEnv, "A2A_AVATAR");
|
|
276
|
+
|
|
277
|
+
console.log(`\n📇 Current Agent Card settings (${A2A_ENV_FILE})\n`);
|
|
278
|
+
console.log(` Description: ${currentDesc || "(not set)"}`);
|
|
279
|
+
console.log(` Skills: ${currentSkills || "(not set)"}`);
|
|
280
|
+
console.log(` Avatar: ${currentAvatar || "(not set — uses GitHub avatar or 🤖)"}\n`);
|
|
281
|
+
console.log(`To update: viveworker a2a card --description "..." --skills "..." --avatar "..."`);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Update a2a.env
|
|
286
|
+
let currentEnv = "";
|
|
287
|
+
try {
|
|
288
|
+
currentEnv = await fs.readFile(A2A_ENV_FILE, "utf8");
|
|
289
|
+
} catch {
|
|
290
|
+
throw new Error(`No a2a.env found at ${A2A_ENV_FILE}. Run 'viveworker a2a setup' first.`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const updates = {};
|
|
294
|
+
if (description) updates.A2A_DESCRIPTION = description;
|
|
295
|
+
if (skillsRaw) updates.A2A_SKILLS = skillsRaw;
|
|
296
|
+
if (avatar) updates.A2A_AVATAR = avatar;
|
|
297
|
+
|
|
298
|
+
const updated = upsertEnvText(currentEnv, updates);
|
|
299
|
+
await fs.writeFile(A2A_ENV_FILE, updated, { mode: 0o600 });
|
|
300
|
+
|
|
301
|
+
console.log(`\n✅ Agent Card updated in ${A2A_ENV_FILE}\n`);
|
|
302
|
+
if (description) console.log(` Description: ${description}`);
|
|
303
|
+
if (skillsRaw) console.log(` Skills: ${skillsRaw}`);
|
|
304
|
+
if (avatar) console.log(` Avatar: ${avatar}`);
|
|
305
|
+
console.log(`\n🔄 The bridge will pick up the change within 30 seconds and re-register with the relay.\n`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function envValue(text, key) {
|
|
309
|
+
for (const line of text.split(/\r?\n/)) {
|
|
310
|
+
const trimmed = line.trim();
|
|
311
|
+
if (trimmed.startsWith("#") || !trimmed) continue;
|
|
312
|
+
const eq = trimmed.indexOf("=");
|
|
313
|
+
if (eq === -1) continue;
|
|
314
|
+
if (trimmed.slice(0, eq) === key) return trimmed.slice(eq + 1);
|
|
315
|
+
}
|
|
316
|
+
return "";
|
|
317
|
+
}
|
|
318
|
+
|
|
149
319
|
// ---------------------------------------------------------------------------
|
|
150
320
|
// Helpers
|
|
151
321
|
// ---------------------------------------------------------------------------
|
package/scripts/a2a-handler.mjs
CHANGED
|
@@ -27,11 +27,17 @@ export function buildAgentCard(config) {
|
|
|
27
27
|
// Custom skills from A2A_SKILLS env var (comma-separated tags → skill objects)
|
|
28
28
|
let skills;
|
|
29
29
|
if (config.a2aSkills) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const LABEL_MAP = {
|
|
31
|
+
typescript: "TypeScript", javascript: "JavaScript",
|
|
32
|
+
nodejs: "Node.js", pwa: "PWA", api: "API", css: "CSS", html: "HTML",
|
|
33
|
+
sql: "SQL", graphql: "GraphQL", nextjs: "Next.js", vuejs: "Vue.js",
|
|
34
|
+
aws: "AWS", gcp: "GCP", cli: "CLI", cicd: "CI/CD", a2a: "A2A",
|
|
35
|
+
llm: "LLM", ai: "AI", ml: "ML",
|
|
36
|
+
};
|
|
37
|
+
skills = config.a2aSkills.split(",").map((s) => s.trim()).filter(Boolean).map((tag) => {
|
|
38
|
+
const label = LABEL_MAP[tag] || tag.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
39
|
+
return { id: tag, name: label, description: label };
|
|
40
|
+
});
|
|
35
41
|
} else {
|
|
36
42
|
skills = [
|
|
37
43
|
{
|
|
@@ -60,10 +66,10 @@ export function buildAgentCard(config) {
|
|
|
60
66
|
];
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
const card = {
|
|
64
70
|
schemaVersion: "1.0",
|
|
65
71
|
humanReadableId: `viveworker/${config.a2aRelayUserId || "viveworker"}`,
|
|
66
|
-
agentVersion: config.version || "0.
|
|
72
|
+
agentVersion: config.version || "0.1.0",
|
|
67
73
|
name: "viveworker",
|
|
68
74
|
description,
|
|
69
75
|
url: `${baseUrl.replace(/\/$/u, "")}/a2a`,
|
|
@@ -76,6 +82,8 @@ export function buildAgentCard(config) {
|
|
|
76
82
|
skills,
|
|
77
83
|
authSchemes: [{ scheme: "apiKey", in: "header", name: "X-A2A-Key" }],
|
|
78
84
|
};
|
|
85
|
+
if (config.a2aAvatar) card.avatar = config.a2aAvatar;
|
|
86
|
+
return card;
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
// ---------------------------------------------------------------------------
|
|
@@ -13789,6 +13789,8 @@ function buildConfig(cli) {
|
|
|
13789
13789
|
a2aPublicUrl: cleanText(process.env.A2A_PUBLIC_URL || ""),
|
|
13790
13790
|
a2aDescription: cleanText(process.env.A2A_DESCRIPTION || ""),
|
|
13791
13791
|
a2aSkills: cleanText(process.env.A2A_SKILLS || ""),
|
|
13792
|
+
a2aAvatar: cleanText(process.env.A2A_AVATAR || ""),
|
|
13793
|
+
version: appPackageVersion,
|
|
13792
13794
|
a2aRelayUrl: cleanText(process.env.A2A_RELAY_URL || ""),
|
|
13793
13795
|
a2aRelayUserId: cleanText(process.env.A2A_RELAY_USER_ID || ""),
|
|
13794
13796
|
a2aRelaySecret: cleanText(process.env.A2A_RELAY_SECRET || ""),
|
|
@@ -15194,31 +15196,52 @@ async function main() {
|
|
|
15194
15196
|
console.error(`[scan-error] ${error.message}`);
|
|
15195
15197
|
}
|
|
15196
15198
|
|
|
15197
|
-
// Hot-reload: detect a2a.env changes
|
|
15199
|
+
// Hot-reload: detect a2a.env changes (new config or updated card)
|
|
15198
15200
|
const now = Date.now();
|
|
15199
|
-
if (
|
|
15201
|
+
if (now - lastA2aEnvCheckAt > A2A_ENV_CHECK_INTERVAL_MS) {
|
|
15200
15202
|
lastA2aEnvCheckAt = now;
|
|
15201
15203
|
try {
|
|
15202
15204
|
loadEnvFile(path.join(os.homedir(), ".viveworker", "a2a.env"));
|
|
15203
15205
|
const newRelayUrl = cleanText(process.env.A2A_RELAY_URL || "");
|
|
15204
15206
|
const newRelayUserId = cleanText(process.env.A2A_RELAY_USER_ID || "");
|
|
15205
15207
|
const newRelayRegisterSecret = cleanText(process.env.A2A_RELAY_REGISTER_SECRET || "");
|
|
15208
|
+
const newDescription = cleanText(process.env.A2A_DESCRIPTION || "");
|
|
15209
|
+
const newSkills = cleanText(process.env.A2A_SKILLS || "");
|
|
15210
|
+
const newAvatar = cleanText(process.env.A2A_AVATAR || "");
|
|
15211
|
+
|
|
15206
15212
|
if (newRelayUrl && newRelayUserId) {
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
config.
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15213
|
+
// Check if this is a first-time connect or a card update
|
|
15214
|
+
const isNew = !config.a2aRelayUrl;
|
|
15215
|
+
const cardChanged = config.a2aDescription !== newDescription || config.a2aSkills !== newSkills || config.a2aAvatar !== newAvatar;
|
|
15216
|
+
|
|
15217
|
+
if (isNew || cardChanged) {
|
|
15218
|
+
config.a2aRelayUrl = newRelayUrl;
|
|
15219
|
+
config.a2aRelayUserId = newRelayUserId;
|
|
15220
|
+
config.a2aRelayRegisterSecret = newRelayRegisterSecret;
|
|
15221
|
+
config.a2aApiKey = cleanText(process.env.A2A_API_KEY || config.a2aApiKey || "");
|
|
15222
|
+
config.a2aDescription = newDescription;
|
|
15223
|
+
config.a2aSkills = newSkills;
|
|
15224
|
+
config.a2aAvatar = newAvatar;
|
|
15225
|
+
|
|
15226
|
+
if (isNew) {
|
|
15227
|
+
console.log(`[a2a-relay] Detected new relay config, registering...`);
|
|
15228
|
+
} else {
|
|
15229
|
+
console.log(`[a2a-relay] Agent Card changed, re-registering...`);
|
|
15230
|
+
}
|
|
15231
|
+
|
|
15232
|
+
const regResult = await registerWithRelay({ config, buildAgentCard });
|
|
15233
|
+
if (regResult.ok) {
|
|
15234
|
+
if (isNew) {
|
|
15235
|
+
startRelayPolling({
|
|
15236
|
+
config,
|
|
15237
|
+
runtime,
|
|
15238
|
+
state,
|
|
15239
|
+
helpers: { recordTimelineEntry, deliverWebPushItem, saveState, historyToken, cleanText },
|
|
15240
|
+
});
|
|
15241
|
+
}
|
|
15242
|
+
} else {
|
|
15243
|
+
console.error(`[a2a-relay] Registration failed: ${regResult.error}`);
|
|
15244
|
+
}
|
|
15222
15245
|
}
|
|
15223
15246
|
}
|
|
15224
15247
|
} catch (error) {
|