u-foo 1.2.16 → 1.4.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.
@@ -74,6 +74,109 @@ function createDaemonMessageRouter(options = {}) {
74
74
  return false;
75
75
  }
76
76
 
77
+ function logGroupMembers(members = []) {
78
+ if (!Array.isArray(members) || members.length === 0) return;
79
+ members.forEach((member) => {
80
+ const nickname = member && member.nickname ? member.nickname : (member && member.template_agent_id ? member.template_agent_id : "unknown");
81
+ const type = member && member.type ? ` [${member.type}]` : "";
82
+ const status = member && member.status ? ` (${member.status})` : "";
83
+ const subscriber = member && member.subscriber_id ? ` -> ${member.subscriber_id}` : "";
84
+ logMessage(
85
+ "system",
86
+ ` • ${escapeBlessed(`${nickname}${type}${status}${subscriber}`)}`
87
+ );
88
+ });
89
+ }
90
+
91
+ function parseGroupErrorEntry(err) {
92
+ const errPath = err && (err.path || err.filePath) ? (err.path || err.filePath) : "template";
93
+ const errMessage = err && (err.message || err.error) ? (err.message || err.error) : "validation error";
94
+ return { errPath, errMessage };
95
+ }
96
+
97
+ function logGroupPayload(group) {
98
+ if (!group || typeof group !== "object") return;
99
+
100
+ if (typeof group.diagram === "string" && group.diagram) {
101
+ const mode = group.mode ? ` ${group.mode}` : "";
102
+ const format = group.format ? ` ${group.format}` : "";
103
+ logMessage("system", `{cyan-fg}Group diagram:{/cyan-fg} ${escapeBlessed(`${mode}${format}`.trim())}`);
104
+ group.diagram.split(/\r?\n/).forEach((line) => {
105
+ logMessage("system", escapeBlessed(line));
106
+ });
107
+ return;
108
+ }
109
+
110
+ if (Object.prototype.hasOwnProperty.call(group, "target") && Array.isArray(group.errors)) {
111
+ if (group.ok) {
112
+ const alias = group.alias || group.target || "";
113
+ const source = group.source ? ` (${group.source})` : "";
114
+ logMessage("system", `{white-fg}✓{/white-fg} Group template valid: ${escapeBlessed(`${alias}${source}`)}`);
115
+ } else {
116
+ logMessage("error", `{white-fg}✗{/white-fg} Group template invalid: ${escapeBlessed(group.target || group.alias || "unknown")}`);
117
+ group.errors.forEach((err) => {
118
+ const { errPath, errMessage } = parseGroupErrorEntry(err);
119
+ logMessage("error", ` - ${escapeBlessed(`${errPath}: ${errMessage}`)}`);
120
+ });
121
+ }
122
+ return;
123
+ }
124
+
125
+ if (Array.isArray(group.groups)) {
126
+ logMessage("system", `{cyan-fg}Groups:{/cyan-fg} ${group.groups.length}`);
127
+ group.groups.forEach((item) => {
128
+ const id = item && item.group_id ? item.group_id : "unknown";
129
+ const status = item && item.status ? item.status : "unknown";
130
+ const alias = item && item.template_alias ? item.template_alias : "-";
131
+ const active = Number(item && item.members_active) || 0;
132
+ const total = Number(item && item.members_total) || 0;
133
+ logMessage(
134
+ "system",
135
+ ` • ${escapeBlessed(id)} [${escapeBlessed(status)}] ${escapeBlessed(alias)} active=${active}/${total}`
136
+ );
137
+ });
138
+ return;
139
+ }
140
+
141
+ if (group.group && typeof group.group === "object") {
142
+ const runtime = group.group;
143
+ const id = runtime.group_id || group.group_id || "unknown";
144
+ const status = runtime.status || group.status || "unknown";
145
+ const alias = runtime.template_alias || group.template_alias || "-";
146
+ logMessage(
147
+ "system",
148
+ `{cyan-fg}Group:{/cyan-fg} ${escapeBlessed(id)} [${escapeBlessed(status)}] ${escapeBlessed(alias)}`
149
+ );
150
+ logGroupMembers(runtime.members);
151
+ if (Array.isArray(group.stopped_agents) && group.stopped_agents.length > 0) {
152
+ logMessage("system", `{white-fg}Stopped:{/white-fg} ${group.stopped_agents.length} agent(s)`);
153
+ }
154
+ return;
155
+ }
156
+
157
+ if (group.group_id && Array.isArray(group.members)) {
158
+ const status = group.dry_run ? "dry_run" : (group.status || "unknown");
159
+ const alias = group.template_alias || "-";
160
+ logMessage(
161
+ "system",
162
+ `{cyan-fg}Group:{/cyan-fg} ${escapeBlessed(group.group_id)} [${escapeBlessed(status)}] ${escapeBlessed(alias)}`
163
+ );
164
+ logGroupMembers(group.members);
165
+ return;
166
+ }
167
+
168
+ if (group.ok === false && group.error) {
169
+ logMessage("error", `{white-fg}✗{/white-fg} ${escapeBlessed(group.error)}`);
170
+ const validationErrors = Array.isArray(group.validationErrors)
171
+ ? group.validationErrors
172
+ : (Array.isArray(group.errors) ? group.errors : []);
173
+ validationErrors.forEach((err) => {
174
+ const { errPath, errMessage } = parseGroupErrorEntry(err);
175
+ logMessage("error", ` - ${escapeBlessed(`${errPath}: ${errMessage}`)}`);
176
+ });
177
+ }
178
+ }
179
+
77
180
  function handleResponseMessage(msg) {
78
181
  const payload = msg.data || {};
79
182
  if (payload.reply) {
@@ -150,6 +253,10 @@ function createDaemonMessageRouter(options = {}) {
150
253
  }
151
254
  }
152
255
 
256
+ if (payload.group && typeof payload.group === "object") {
257
+ logGroupPayload(payload.group);
258
+ }
259
+
153
260
  if (payload.dispatch && payload.dispatch.length > 0) {
154
261
  const targets = payload.dispatch.map((d) => d.target || d).join(", ");
155
262
  logMessage("dispatch", `{white-fg}→{/white-fg} Dispatched to: ${escapeBlessed(targets)}`);
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+
3
+ const path = require("path");
4
+ const {
5
+ loadTemplateRegistry,
6
+ resolveTemplateReference,
7
+ createTemplateFromBuiltin,
8
+ } = require("../group/templates");
9
+ const { validateTemplate } = require("../group/validateTemplate");
10
+
11
+ function parseTemplateNewArgs(args = []) {
12
+ const alias = String(args[0] || "").trim();
13
+ const options = {
14
+ from: "",
15
+ scope: "project",
16
+ force: false,
17
+ };
18
+ let sawGlobal = false;
19
+ let sawProject = false;
20
+
21
+ for (let i = 1; i < args.length; i += 1) {
22
+ const token = args[i];
23
+ if (token === "--from") {
24
+ options.from = String(args[i + 1] || "").trim();
25
+ i += 1;
26
+ continue;
27
+ }
28
+ if (token === "--global") {
29
+ sawGlobal = true;
30
+ options.scope = "global";
31
+ continue;
32
+ }
33
+ if (token === "--project") {
34
+ sawProject = true;
35
+ options.scope = "project";
36
+ continue;
37
+ }
38
+ if (token === "--force") {
39
+ options.force = true;
40
+ continue;
41
+ }
42
+ if (token === "--json") {
43
+ continue;
44
+ }
45
+ throw new Error(`Unknown option for template new: ${token}`);
46
+ }
47
+
48
+ if (!alias) throw new Error("template new requires <alias>");
49
+ if (!options.from) throw new Error("template new requires --from <builtin-alias>");
50
+ if (sawGlobal && sawProject) {
51
+ throw new Error("template new cannot use both --global and --project");
52
+ }
53
+
54
+ return { alias, ...options };
55
+ }
56
+
57
+ function formatDisplayPath(filePath = "", cwd = "") {
58
+ const target = String(filePath || "");
59
+ if (!target) return "";
60
+ const base = String(cwd || "").trim();
61
+ if (!base) return target;
62
+ const relative = path.relative(base, target);
63
+ if (!relative || relative.startsWith("..")) return target;
64
+ return relative;
65
+ }
66
+
67
+ function printList({ templates, errors }, { write, json, cwd }) {
68
+ if (json) {
69
+ write(JSON.stringify({ templates, errors }, null, 2));
70
+ return;
71
+ }
72
+
73
+ if (!templates.length) {
74
+ write("No group templates found.");
75
+ } else {
76
+ for (const item of templates) {
77
+ const nameLabel = item.templateName || item.alias;
78
+ const idLabel = item.templateId || "-";
79
+ const verLabel = Number.isInteger(item.schemaVersion) ? item.schemaVersion : "-";
80
+ const displayPath = formatDisplayPath(item.filePath, cwd);
81
+ write(`- ${item.alias} [${item.source}]`);
82
+ write(` name: ${nameLabel}`);
83
+ write(` id: ${idLabel} schema: ${verLabel}`);
84
+ write(` file: ${displayPath}`);
85
+ }
86
+ }
87
+
88
+ if (errors.length > 0) {
89
+ write(`Warnings: ${errors.length} template file(s) failed to load`);
90
+ for (const err of errors) {
91
+ const displayPath = formatDisplayPath(err.filePath, cwd);
92
+ write(` - ${displayPath}: ${err.error}`);
93
+ }
94
+ }
95
+ }
96
+
97
+ function formatResolveErrors(errors = []) {
98
+ if (!Array.isArray(errors) || errors.length === 0) return "";
99
+ return errors
100
+ .map((item) => `${item.filePath}: ${item.error}`)
101
+ .join("; ");
102
+ }
103
+
104
+ function throwResolveFailure(target, resolved = {}) {
105
+ const details = formatResolveErrors(resolved.errors || []);
106
+ if (details) {
107
+ throw new Error(`Failed to load template "${target}": ${details}`);
108
+ }
109
+ throw new Error(`Template not found: ${target}`);
110
+ }
111
+
112
+ function printValidation(result, target, entry, { write, json }) {
113
+ if (json) {
114
+ write(
115
+ JSON.stringify(
116
+ {
117
+ target,
118
+ alias: entry.alias,
119
+ source: entry.source,
120
+ filePath: entry.filePath,
121
+ ok: result.ok,
122
+ errors: result.errors,
123
+ },
124
+ null,
125
+ 2
126
+ )
127
+ );
128
+ return;
129
+ }
130
+
131
+ if (result.ok) {
132
+ write(`✓ Template "${entry.alias}" is valid (${entry.source})`);
133
+ return;
134
+ }
135
+
136
+ write(`✗ Template "${entry.alias}" is invalid (${result.errors.length} error(s))`);
137
+ for (const err of result.errors) {
138
+ write(` - ${err.path}: ${err.message}`);
139
+ }
140
+ }
141
+
142
+ async function runGroupCoreCommand(subcmd, cmdArgs = [], options = {}) {
143
+ const cwd = options.cwd || process.cwd();
144
+ const write = typeof options.write === "function" ? options.write : console.log;
145
+ const json = Boolean(options.json);
146
+ const templatesOptions = options.templatesOptions || {};
147
+
148
+ const args = Array.isArray(cmdArgs) ? cmdArgs.filter((item) => item !== undefined) : [];
149
+ const normalizedSubcmd = String(subcmd || "").trim().toLowerCase();
150
+
151
+ if (normalizedSubcmd === "templates") {
152
+ const action = String(args[0] || "list").trim().toLowerCase();
153
+ if (action !== "list" && action !== "ls") {
154
+ throw new Error(`Unknown group templates action: ${action}`);
155
+ }
156
+
157
+ const registry = loadTemplateRegistry(cwd, templatesOptions);
158
+ const templates = registry.templates.map((item) => ({
159
+ alias: item.alias,
160
+ source: item.source,
161
+ filePath: item.filePath,
162
+ templateId: item.templateId || "",
163
+ templateName: item.templateName || "",
164
+ schemaVersion: item.schemaVersion,
165
+ }));
166
+ printList({ templates, errors: registry.errors }, { write, json, cwd });
167
+ return;
168
+ }
169
+
170
+ if (normalizedSubcmd !== "template") {
171
+ throw new Error(`Unknown group subcommand: ${subcmd}`);
172
+ }
173
+
174
+ const action = String(args[0] || "list").trim().toLowerCase();
175
+
176
+ if (action === "list") {
177
+ const registry = loadTemplateRegistry(cwd, templatesOptions);
178
+ const templates = registry.templates.map((item) => ({
179
+ alias: item.alias,
180
+ source: item.source,
181
+ filePath: item.filePath,
182
+ templateId: item.templateId || "",
183
+ templateName: item.templateName || "",
184
+ schemaVersion: item.schemaVersion,
185
+ }));
186
+ printList({ templates, errors: registry.errors }, { write, json, cwd });
187
+ return;
188
+ }
189
+
190
+ if (action === "show") {
191
+ const target = String(args[1] || "").trim();
192
+ if (!target) throw new Error("group template show requires <alias>");
193
+ const resolved = resolveTemplateReference(cwd, target, {
194
+ allowPath: false,
195
+ cwd,
196
+ ...templatesOptions,
197
+ });
198
+ if (!resolved.entry) {
199
+ throwResolveFailure(target, resolved);
200
+ }
201
+ write(JSON.stringify(resolved.entry.data, null, 2));
202
+ return;
203
+ }
204
+
205
+ if (action === "validate") {
206
+ const target = String(args[1] || "").trim();
207
+ if (!target) throw new Error("group template validate requires <alias|path>");
208
+ const resolved = resolveTemplateReference(cwd, target, {
209
+ allowPath: true,
210
+ cwd,
211
+ ...templatesOptions,
212
+ });
213
+ if (!resolved.entry) {
214
+ throwResolveFailure(target, resolved);
215
+ }
216
+ const result = validateTemplate(resolved.entry.data);
217
+ printValidation(result, target, resolved.entry, { write, json });
218
+ if (!result.ok) {
219
+ throw new Error(`Template validation failed: ${resolved.entry.alias}`);
220
+ }
221
+ return;
222
+ }
223
+
224
+ if (action === "new") {
225
+ const params = parseTemplateNewArgs(args.slice(1));
226
+ const created = createTemplateFromBuiltin(cwd, params.alias, params.from, {
227
+ ...templatesOptions,
228
+ scope: params.scope,
229
+ force: params.force,
230
+ });
231
+
232
+ if (json) {
233
+ write(JSON.stringify(created, null, 2));
234
+ return;
235
+ }
236
+ const relative = path.relative(cwd, created.filePath) || created.filePath;
237
+ write(`Created template "${created.alias}" from "${created.from}" at ${relative}`);
238
+ return;
239
+ }
240
+
241
+ throw new Error(`Unknown group template action: ${action}`);
242
+ }
243
+
244
+ module.exports = {
245
+ runGroupCoreCommand,
246
+ };
@@ -95,10 +95,12 @@ async function runOnlineRoom(action, opts = {}, onlineAuthHeaders) {
95
95
  return;
96
96
  }
97
97
  if (action === "create") {
98
+ const creator = String(opts.createdBy || opts.creator || opts.nickname || opts.subscriber || "").trim();
98
99
  const payload = {
99
100
  name: opts.name,
100
101
  type: opts.type,
101
102
  password: opts.password,
103
+ created_by: creator || undefined,
102
104
  };
103
105
  if (!payload.type) {
104
106
  throw new Error("online room create requires --type");
@@ -132,9 +134,11 @@ async function runOnlineChannel(action, opts = {}, onlineAuthHeaders) {
132
134
  return;
133
135
  }
134
136
  if (action === "create") {
137
+ const creator = String(opts.createdBy || opts.creator || opts.nickname || opts.subscriber || "").trim();
135
138
  const payload = {
136
139
  name: opts.name,
137
140
  type: opts.type,
141
+ created_by: creator || undefined,
138
142
  };
139
143
  if (!payload.name) {
140
144
  throw new Error("online channel create requires --name");
@@ -233,6 +237,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
233
237
  name: opts.name,
234
238
  type: opts.type,
235
239
  password: opts.password,
240
+ createdBy: opts.createdBy || "",
236
241
  }, onlineAuthHeaders);
237
242
  case "channel":
238
243
  return runOnlineChannel(payload.action, {
@@ -243,6 +248,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
243
248
  nickname: opts.nickname || "",
244
249
  name: opts.name,
245
250
  type: opts.type,
251
+ createdBy: opts.createdBy || "",
246
252
  }, onlineAuthHeaders);
247
253
  case "connect":
248
254
  return runOnlineConnect({
@@ -312,6 +318,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
312
318
  name: getFallbackOpt(argv, "--name"),
313
319
  type: getFallbackOpt(argv, "--type"),
314
320
  password: getFallbackOpt(argv, "--password"),
321
+ createdBy: getFallbackOpt(argv, "--created-by"),
315
322
  }, onlineAuthHeaders);
316
323
  }
317
324
  case "channel": {
@@ -325,6 +332,7 @@ async function runOnlineCommand(subcmd, payload = {}, options = {}) {
325
332
  nickname: getFallbackOpt(argv, "--nickname"),
326
333
  name: getFallbackOpt(argv, "--name"),
327
334
  type: getFallbackOpt(argv, "--type") || defaultChannelType,
335
+ createdBy: getFallbackOpt(argv, "--created-by"),
328
336
  }, onlineAuthHeaders);
329
337
  }
330
338
  case "connect": {