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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.4.1",
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",
@@ -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("Usage: viveworker a2a setup --user-id <id> [--description <text>] [--skills <csv>] [--relay-url <url>] [--timeout <seconds>]");
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
  // ---------------------------------------------------------------------------
@@ -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
- skills = config.a2aSkills.split(",").map((s) => s.trim()).filter(Boolean).map((tag) => ({
31
- id: tag,
32
- name: tag,
33
- description: tag,
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
- return {
69
+ const card = {
64
70
  schemaVersion: "1.0",
65
71
  humanReadableId: `viveworker/${config.a2aRelayUserId || "viveworker"}`,
66
- agentVersion: config.version || "0.3.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 and auto-start relay
15199
+ // Hot-reload: detect a2a.env changes (new config or updated card)
15198
15200
  const now = Date.now();
15199
- if (!config.a2aRelayUrl && now - lastA2aEnvCheckAt > A2A_ENV_CHECK_INTERVAL_MS) {
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
- config.a2aRelayUrl = newRelayUrl;
15208
- config.a2aRelayUserId = newRelayUserId;
15209
- config.a2aRelayRegisterSecret = newRelayRegisterSecret;
15210
- config.a2aApiKey = cleanText(process.env.A2A_API_KEY || config.a2aApiKey || "");
15211
- console.log(`[a2a-relay] Detected new relay config, registering...`);
15212
- const regResult = await registerWithRelay({ config, buildAgentCard });
15213
- if (regResult.ok) {
15214
- startRelayPolling({
15215
- config,
15216
- runtime,
15217
- state,
15218
- helpers: { recordTimelineEntry, deliverWebPushItem, saveState, historyToken, cleanText },
15219
- });
15220
- } else {
15221
- console.error(`[a2a-relay] Registration failed: ${regResult.error}`);
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) {