synomem 0.1.0 → 0.2.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.
Files changed (73) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/CHANGELOG.md +37 -1
  3. package/README.md +28 -15
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +311 -70
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts +96 -17
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +333 -76
  10. package/dist/client.js.map +1 -1
  11. package/dist/config.d.ts +2 -2
  12. package/dist/config.js +8 -8
  13. package/dist/import.d.ts +207 -13
  14. package/dist/import.d.ts.map +1 -1
  15. package/dist/index.d.ts +2 -2
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/mcp/index.d.ts.map +1 -1
  20. package/dist/mcp/index.js +156 -31
  21. package/dist/mcp/index.js.map +1 -1
  22. package/dist/mcp-server.js +54 -8
  23. package/dist/mcp-server.js.map +1 -1
  24. package/dist/ports/repository.d.ts +18 -1
  25. package/dist/ports/repository.d.ts.map +1 -1
  26. package/dist/projections.d.ts +18 -1
  27. package/dist/projections.d.ts.map +1 -1
  28. package/dist/projections.js +137 -44
  29. package/dist/projections.js.map +1 -1
  30. package/dist/remote.d.ts +57 -9
  31. package/dist/remote.d.ts.map +1 -1
  32. package/dist/remote.js +43 -2
  33. package/dist/remote.js.map +1 -1
  34. package/dist/schemas.d.ts +302 -21
  35. package/dist/schemas.d.ts.map +1 -1
  36. package/dist/schemas.js +142 -26
  37. package/dist/schemas.js.map +1 -1
  38. package/dist/service.d.ts +57 -10
  39. package/dist/service.d.ts.map +1 -1
  40. package/dist/skill-install.d.ts +8 -2
  41. package/dist/skill-install.d.ts.map +1 -1
  42. package/dist/skill-install.js +6 -11
  43. package/dist/skill-install.js.map +1 -1
  44. package/dist/storage.d.ts +41 -1
  45. package/dist/storage.d.ts.map +1 -1
  46. package/dist/storage.js +254 -36
  47. package/dist/storage.js.map +1 -1
  48. package/dist/types.d.ts +213 -38
  49. package/dist/types.d.ts.map +1 -1
  50. package/docs/cli.md +53 -20
  51. package/docs/examples.md +4 -4
  52. package/docs/mcp.md +12 -4
  53. package/docs/skill.md +10 -7
  54. package/docs/storage-format.md +4 -4
  55. package/openapi/synomem-v1.yaml +24 -24
  56. package/package.json +1 -1
  57. package/skills/synomem/SKILL.md +24 -8
  58. package/skills/synomem/agents/openai.yaml +1 -1
  59. package/skills/synomem/references/examples.md +1 -1
  60. package/src/cli.ts +572 -173
  61. package/src/client.ts +391 -79
  62. package/src/config.ts +8 -8
  63. package/src/index.ts +11 -1
  64. package/src/mcp/index.ts +189 -30
  65. package/src/mcp-server.ts +58 -8
  66. package/src/ports/repository.ts +16 -0
  67. package/src/projections.ts +139 -44
  68. package/src/remote.ts +118 -8
  69. package/src/schemas.ts +147 -26
  70. package/src/service.ts +59 -7
  71. package/src/skill-install.ts +14 -17
  72. package/src/storage.ts +326 -34
  73. package/src/types.ts +228 -39
package/src/schemas.ts CHANGED
@@ -24,6 +24,30 @@ export const agentIdSchema = z
24
24
  .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Use lowercase ASCII letters, digits, and hyphens')
25
25
  .refine((id) => !reservedIds.has(id), 'Reserved agent ID');
26
26
 
27
+ /**
28
+ * An alias as written, folded to the canonical lowercase form.
29
+ *
30
+ * People type `Mike` and `mike` interchangeably, so accepting either and
31
+ * storing one keeps a single alias from being claimed twice in two casings.
32
+ */
33
+ export const agentAliasSchema = z
34
+ .string()
35
+ .trim()
36
+ .min(1)
37
+ .max(63)
38
+ .regex(/^[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*$/, 'Use ASCII letters, digits, and hyphens')
39
+ .transform((alias) => alias.toLowerCase())
40
+ .refine((alias) => !reservedIds.has(alias), 'Reserved agent ID');
41
+
42
+ /**
43
+ * A name offered for lookup, which may be an ID or an alias in any casing.
44
+ *
45
+ * Lookups are deliberately more permissive than writes: rejecting `Mycroft`
46
+ * for its capital letter would tell the caller nothing useful about whether
47
+ * that agent exists.
48
+ */
49
+ export const agentLookupSchema = z.string().trim().min(1).max(100);
50
+
27
51
  export const actorSchema = z.object({
28
52
  kind: z.enum(['human', 'agent', 'system']),
29
53
  id: agentIdSchema,
@@ -88,7 +112,7 @@ export const evidenceSchema = z
88
112
  export const profileSchema = z.object({
89
113
  id: agentIdSchema,
90
114
  displayName: z.string().trim().min(1).max(200),
91
- aliases: z.array(agentIdSchema).max(50).optional(),
115
+ aliases: z.array(agentAliasSchema).max(50).optional(),
92
116
  description: z.string().trim().max(2000).optional(),
93
117
  createdAt: z.string().datetime({ offset: true }),
94
118
  metadata: metadataSchema.optional(),
@@ -97,6 +121,21 @@ export const profileSchema = z.object({
97
121
  export const createAgentSchema = profileSchema.omit({ createdAt: true });
98
122
  export const updateAgentSchema = createAgentSchema.omit({ id: true }).partial();
99
123
 
124
+ /**
125
+ * A runtime binding is a claim about where an agent runs, so the fields stay
126
+ * deliberately loose: Synomem should record a runtime it has never heard of
127
+ * rather than reject an install it cannot classify.
128
+ */
129
+ export const bindRuntimeSchema = z
130
+ .object({
131
+ agentId: z.string().trim().min(1).max(100),
132
+ runtime: z.string().trim().min(1).max(100),
133
+ profile: z.string().trim().min(1).max(100).optional(),
134
+ installationId: z.string().trim().min(1).max(100).optional(),
135
+ capabilities: metadataSchema.optional(),
136
+ })
137
+ .strict();
138
+
100
139
  const baseEventSchema = z.object({
101
140
  schemaVersion: z.literal(1),
102
141
  id: z.string().regex(/^[0-9A-HJKMNP-TV-Z]{26}$/),
@@ -218,7 +257,7 @@ const noteArchivedSchema = baseEventSchema.extend({
218
257
  noteId: z.string().length(26),
219
258
  });
220
259
 
221
- const todoDueSchema = z.discriminatedUnion('kind', [
260
+ const taskDueSchema = z.discriminatedUnion('kind', [
222
261
  z.object({
223
262
  kind: z.literal('date'),
224
263
  date: z
@@ -247,7 +286,7 @@ const todoDueSchema = z.discriminatedUnion('kind', [
247
286
  }, 'Use a valid IANA time-zone identifier'),
248
287
  }),
249
288
  ]);
250
- const todoFields = {
289
+ const taskFields = {
251
290
  title: z
252
291
  .string()
253
292
  .trim()
@@ -256,15 +295,64 @@ const todoFields = {
256
295
  .regex(/^[^\r\n]+$/),
257
296
  description: z.string().trim().max(16_000).optional(),
258
297
  priority: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
259
- due: todoDueSchema.optional(),
298
+ due: taskDueSchema.optional(),
260
299
  tags: z.array(kudosTagSchema).max(20).optional(),
261
300
  visibility: z.enum(['private', 'workspace', 'public']),
262
301
  };
263
- const todoCreatedSchema = baseEventSchema.extend({
264
- type: z.literal('todo.created'),
302
+ const taskCreatedSchema = baseEventSchema.extend({
303
+ type: z.literal('task.created'),
265
304
  assigneeAgentId: agentIdSchema,
266
305
  assigneeDisplayName: z.string().trim().min(1).max(200),
267
306
  requiresAcceptance: z.boolean(),
307
+ ...taskFields,
308
+ });
309
+ const taskUpdatedSchema = baseEventSchema.extend({
310
+ type: z.literal('task.updated'),
311
+ taskId: z.string().length(26),
312
+ ...taskFields,
313
+ });
314
+ const taskCompletedSchema = baseEventSchema.extend({
315
+ type: z.literal('task.completed'),
316
+ taskId: z.string().length(26),
317
+ note: z.string().trim().min(1).max(2000).optional(),
318
+ });
319
+ const taskReopenedSchema = baseEventSchema.extend({
320
+ type: z.literal('task.reopened'),
321
+ taskId: z.string().length(26),
322
+ });
323
+ const taskAcceptedSchema = baseEventSchema.extend({
324
+ type: z.literal('task.accepted'),
325
+ taskId: z.string().length(26),
326
+ // Optional: accepting without comment is a complete answer on its own.
327
+ response: z.string().trim().min(1).max(2000).optional(),
328
+ });
329
+ const taskRejectedSchema = baseEventSchema.extend({
330
+ type: z.literal('task.rejected'),
331
+ taskId: z.string().length(26),
332
+ // Required. A refusal with no reason tells the assigner only that the work
333
+ // will not happen, not whether to reassign it, wait, or change the request.
334
+ response: z.string().trim().min(1).max(2000),
335
+ });
336
+ const taskCanceledSchema = baseEventSchema.extend({
337
+ type: z.literal('task.canceled'),
338
+ taskId: z.string().length(26),
339
+ reason: z.string().trim().min(1).max(2000).optional(),
340
+ });
341
+
342
+ /**
343
+ * A Todo is owner-only, so it carries no assignee and no acceptance lifecycle.
344
+ * `details` rather than `description` keeps it lexically distinct from a Task in
345
+ * every payload, which makes a mix-up visible in a log rather than silent.
346
+ */
347
+ const todoFields = {
348
+ title: z.string().trim().min(1).max(200),
349
+ details: z.string().trim().min(1).max(4000).optional(),
350
+ priority: z.union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)]),
351
+ due: taskDueSchema.optional(),
352
+ tags: z.array(kudosTagSchema).max(20).optional(),
353
+ };
354
+ const todoCreatedSchema = baseEventSchema.extend({
355
+ type: z.literal('todo.created'),
268
356
  ...todoFields,
269
357
  });
270
358
  const todoUpdatedSchema = baseEventSchema.extend({
@@ -281,20 +369,15 @@ const todoReopenedSchema = baseEventSchema.extend({
281
369
  type: z.literal('todo.reopened'),
282
370
  todoId: z.string().length(26),
283
371
  });
284
- const todoAcceptedSchema = baseEventSchema.extend({
285
- type: z.literal('todo.accepted'),
286
- todoId: z.string().length(26),
287
- });
288
- const todoRejectedSchema = baseEventSchema.extend({
289
- type: z.literal('todo.rejected'),
290
- todoId: z.string().length(26),
291
- reason: z.string().trim().min(1).max(2000).optional(),
292
- });
293
372
  const todoCanceledSchema = baseEventSchema.extend({
294
373
  type: z.literal('todo.canceled'),
295
374
  todoId: z.string().length(26),
296
375
  reason: z.string().trim().min(1).max(2000).optional(),
297
376
  });
377
+ const todoArchivedSchema = baseEventSchema.extend({
378
+ type: z.literal('todo.archived'),
379
+ todoId: z.string().length(26),
380
+ });
298
381
 
299
382
  export const eventSchema = z.discriminatedUnion('type', [
300
383
  kudosGivenSchema,
@@ -308,13 +391,19 @@ export const eventSchema = z.discriminatedUnion('type', [
308
391
  noteCreatedSchema,
309
392
  noteRevisedSchema,
310
393
  noteArchivedSchema,
394
+ taskCreatedSchema,
395
+ taskUpdatedSchema,
396
+ taskCompletedSchema,
397
+ taskReopenedSchema,
398
+ taskAcceptedSchema,
399
+ taskRejectedSchema,
400
+ taskCanceledSchema,
311
401
  todoCreatedSchema,
312
402
  todoUpdatedSchema,
313
403
  todoCompletedSchema,
314
404
  todoReopenedSchema,
315
- todoAcceptedSchema,
316
- todoRejectedSchema,
317
405
  todoCanceledSchema,
406
+ todoArchivedSchema,
318
407
  ]);
319
408
 
320
409
  const giveKudosInputSchema = kudosGivenSchema
@@ -407,15 +496,44 @@ export const reviseNoteSchema = z
407
496
  ...mutationMetadata,
408
497
  })
409
498
  .strict();
410
- export const createTodoSchema = z
499
+ export const createTaskSchema = z
411
500
  .object({
412
501
  assigneeAgentId: agentIdSchema.optional(),
502
+ title: taskCreatedSchema.shape.title,
503
+ description: taskCreatedSchema.shape.description,
504
+ priority: taskCreatedSchema.shape.priority.optional(),
505
+ due: taskDueSchema.optional(),
506
+ tags: z.array(kudosTagSchema).max(20).optional(),
507
+ visibility: z.enum(['private', 'workspace', 'public']).optional(),
508
+ ...mutationMetadata,
509
+ })
510
+ .strict();
511
+ export const updateTaskSchema = z
512
+ .object({
513
+ taskId: z.string().length(26),
514
+ expectedVersion: z.number().int().min(1),
515
+ title: taskCreatedSchema.shape.title.optional(),
516
+ description: taskCreatedSchema.shape.description,
517
+ priority: taskCreatedSchema.shape.priority.optional(),
518
+ due: taskDueSchema.nullable().optional(),
519
+ tags: z.array(kudosTagSchema).max(20).optional(),
520
+ visibility: z.enum(['private', 'workspace', 'public']).optional(),
521
+ ...mutationMetadata,
522
+ })
523
+ .strict();
524
+ /**
525
+ * A Todo takes no assignee and no visibility: it belongs to its author and is
526
+ * always private. Omitting those fields from the input — rather than accepting
527
+ * and ignoring them — means an attempt to assign a Todo fails loudly instead of
528
+ * silently producing a private reminder nobody else can see.
529
+ */
530
+ export const createTodoSchema = z
531
+ .object({
413
532
  title: todoCreatedSchema.shape.title,
414
- description: todoCreatedSchema.shape.description,
533
+ details: todoCreatedSchema.shape.details,
415
534
  priority: todoCreatedSchema.shape.priority.optional(),
416
- due: todoDueSchema.optional(),
535
+ due: taskDueSchema.optional(),
417
536
  tags: z.array(kudosTagSchema).max(20).optional(),
418
- visibility: z.enum(['private', 'workspace', 'public']).optional(),
419
537
  ...mutationMetadata,
420
538
  })
421
539
  .strict();
@@ -424,23 +542,26 @@ export const updateTodoSchema = z
424
542
  todoId: z.string().length(26),
425
543
  expectedVersion: z.number().int().min(1),
426
544
  title: todoCreatedSchema.shape.title.optional(),
427
- description: todoCreatedSchema.shape.description,
545
+ details: todoCreatedSchema.shape.details,
428
546
  priority: todoCreatedSchema.shape.priority.optional(),
429
- due: todoDueSchema.nullable().optional(),
547
+ due: taskDueSchema.nullable().optional(),
430
548
  tags: z.array(kudosTagSchema).max(20).optional(),
431
- visibility: z.enum(['private', 'workspace', 'public']).optional(),
432
549
  ...mutationMetadata,
433
550
  })
434
551
  .strict();
552
+
435
553
  export const itemListInputSchema = z
436
554
  .object({
437
555
  kinds: z
438
- .array(z.enum(['kudos', 'memo', 'note', 'todo']))
439
- .max(4)
556
+ .array(z.enum(['kudos', 'memo', 'note', 'task', 'todo']))
557
+ .max(5)
440
558
  .optional(),
441
559
  participantAgentId: agentIdSchema.optional(),
442
560
  actorId: agentIdSchema.optional(),
443
561
  actorKind: z.enum(['human', 'agent', 'system']).optional(),
562
+ awaitingResponse: z.boolean().optional(),
563
+ awaitingSince: z.string().datetime({ offset: true }).optional(),
564
+ overdueAsOf: z.string().datetime({ offset: true }).optional(),
444
565
  tag: kudosTagSchema.optional(),
445
566
  status: z.string().trim().min(1).max(50).optional(),
446
567
  pending: z.boolean().optional(),
package/src/service.ts CHANGED
@@ -1,13 +1,19 @@
1
1
  import type {
2
2
  ActorIdentity,
3
+ AgentDirectoryEntry,
3
4
  AgentProfile,
5
+ AgentResolution,
6
+ AgentRuntimeBinding,
7
+ BindRuntimeInput,
4
8
  ChangePage,
5
9
  ChangesInput,
6
10
  CreateAgentInput,
7
11
  CreateNoteInput,
8
12
  CreateNoteResult,
13
+ CreateTaskInput,
9
14
  CreateTodoInput,
10
15
  CreateTodoResult,
16
+ CreateTaskResult,
11
17
  DoctorResult,
12
18
  GiveKudosInput,
13
19
  GiveKudosResult,
@@ -27,8 +33,10 @@ import type {
27
33
  ReviseNoteInput,
28
34
  SendMemoInput,
29
35
  SendMemoResult,
36
+ TaskRecord,
30
37
  TodoRecord,
31
38
  UpdateAgentInput,
39
+ UpdateTaskInput,
32
40
  UpdateTodoInput,
33
41
  } from './types.js';
34
42
 
@@ -45,7 +53,7 @@ export interface SynomemServiceCapabilities {
45
53
  projections: {
46
54
  writeWinsMarkdown: boolean;
47
55
  writeMemoryMarkdown: boolean;
48
- writeTodosMarkdown: boolean;
56
+ writeTasksMarkdown: boolean;
49
57
  writeInboxEntries: boolean;
50
58
  };
51
59
  }
@@ -66,6 +74,11 @@ export interface SynomemDomainService {
66
74
  update(id: string, changes: UpdateAgentInput): Promise<AgentProfile>;
67
75
  get(idOrAlias: string): Promise<AgentProfile>;
68
76
  list(): Promise<AgentProfile[]>;
77
+ resolve(query: string): Promise<AgentResolution>;
78
+ directory(): Promise<AgentDirectoryEntry[]>;
79
+ bindings(idOrAlias: string): Promise<AgentRuntimeBinding[]>;
80
+ bindRuntime(input: BindRuntimeInput): Promise<AgentRuntimeBinding>;
81
+ unbindRuntime(bindingId: string): Promise<boolean>;
69
82
  };
70
83
  readonly kudos: {
71
84
  give(input: GiveKudosInput): Promise<GiveKudosResult>;
@@ -93,17 +106,43 @@ export interface SynomemDomainService {
93
106
  revise(input: ReviseNoteInput): Promise<NoteRecord>;
94
107
  archive(input: { noteId: string; idempotencyKey?: string }): Promise<NoteRecord>;
95
108
  };
109
+ readonly tasks: {
110
+ create(input: CreateTaskInput): Promise<CreateTaskResult>;
111
+ list(input?: Omit<ItemListInput, 'kinds'>): Promise<Page<ItemSummary>>;
112
+ get(id: string): Promise<TaskRecord>;
113
+ update(input: UpdateTaskInput): Promise<TaskRecord>;
114
+ accept(input: {
115
+ taskId: string;
116
+ /** Optional: conditions, timing, or partial capability. */
117
+ response?: string;
118
+ idempotencyKey?: string;
119
+ }): Promise<TaskRecord>;
120
+ reject(input: {
121
+ taskId: string;
122
+ /** Required: a refusal the assigner cannot act on is barely an answer. */
123
+ response: string;
124
+ idempotencyKey?: string;
125
+ }): Promise<TaskRecord>;
126
+ complete(input: {
127
+ taskId: string;
128
+ note?: string;
129
+ idempotencyKey?: string;
130
+ }): Promise<TaskRecord>;
131
+ reopen(input: { taskId: string; idempotencyKey?: string }): Promise<TaskRecord>;
132
+ cancel(input: {
133
+ taskId: string;
134
+ reason?: string;
135
+ idempotencyKey?: string;
136
+ }): Promise<TaskRecord>;
137
+ };
138
+ /**
139
+ * Private self-reminders. No assignee, no acceptance, owner-only reads.
140
+ */
96
141
  readonly todos: {
97
142
  create(input: CreateTodoInput): Promise<CreateTodoResult>;
98
143
  list(input?: Omit<ItemListInput, 'kinds'>): Promise<Page<ItemSummary>>;
99
144
  get(id: string): Promise<TodoRecord>;
100
145
  update(input: UpdateTodoInput): Promise<TodoRecord>;
101
- accept(input: { todoId: string; idempotencyKey?: string }): Promise<TodoRecord>;
102
- reject(input: {
103
- todoId: string;
104
- reason?: string;
105
- idempotencyKey?: string;
106
- }): Promise<TodoRecord>;
107
146
  complete(input: {
108
147
  todoId: string;
109
148
  note?: string;
@@ -115,6 +154,19 @@ export interface SynomemDomainService {
115
154
  reason?: string;
116
155
  idempotencyKey?: string;
117
156
  }): Promise<TodoRecord>;
157
+ archive(input: { todoId: string; idempotencyKey?: string }): Promise<TodoRecord>;
158
+ };
159
+ /**
160
+ * Unanswered and overdue discovery. Derived from durable events; never a
161
+ * statement about whether an agent is reachable.
162
+ */
163
+ readonly discovery: {
164
+ unanswered(
165
+ input?: Omit<ItemListInput, 'awaitingResponse' | 'pending'> & { olderThanHours?: number },
166
+ ): Promise<Page<ItemSummary>>;
167
+ overdue(
168
+ input?: Omit<ItemListInput, 'overdueAsOf'> & { asOf?: string },
169
+ ): Promise<Page<ItemSummary>>;
118
170
  };
119
171
  readonly items: {
120
172
  list(input?: ItemListInput): Promise<Page<ItemSummary>>;
@@ -57,8 +57,14 @@ export interface SkillOptions {
57
57
  apply?: boolean;
58
58
  force?: boolean;
59
59
  link?: boolean;
60
- actorId?: string;
61
- actorName?: string;
60
+ /**
61
+ * The agent this installation binds to.
62
+ *
63
+ * Only the canonical ID is written into the generated registration command.
64
+ * The display name and kind are read from the agent's profile at startup, so
65
+ * a harness cannot sign another agent's name to work it did.
66
+ */
67
+ agentId?: string;
62
68
  userHome?: string;
63
69
  env?: NodeJS.ProcessEnv;
64
70
  source?: string;
@@ -160,19 +166,10 @@ function shellQuote(value: string): string {
160
166
  return `'${value.replaceAll("'", `'\\''`)}'`;
161
167
  }
162
168
 
163
- function mcpCommand(
164
- runtime: SkillRuntime,
165
- actorId?: string,
166
- actorName?: string,
167
- ): string | undefined {
168
- if (!actorId) return undefined;
169
- const identity = actorSchema.parse({
170
- kind: 'agent',
171
- id: actorId,
172
- displayName: actorName ?? actorId,
173
- });
174
- const name = identity.displayName ?? identity.id;
175
- const args = ['--actor-id', identity.id, '--actor-kind', 'agent', '--actor-name', name];
169
+ function mcpCommand(runtime: SkillRuntime, agentId?: string): string | undefined {
170
+ if (!agentId) return undefined;
171
+ const identity = actorSchema.parse({ kind: 'agent', id: agentId });
172
+ const args = ['--agent-id', identity.id];
176
173
  if (runtime === 'codex') {
177
174
  return `codex mcp add synomem -- synomem-mcp ${args.map(shellQuote).join(' ')}`;
178
175
  }
@@ -258,7 +255,7 @@ export function skillStatus(options: SkillOptions = {}): SkillOperationResult {
258
255
  packageVersion: packageVersion(),
259
256
  locations,
260
257
  mcpCommands: locations
261
- .map((location) => mcpCommand(location.runtime, options.actorId, options.actorName))
258
+ .map((location) => mcpCommand(location.runtime, options.agentId))
262
259
  .filter((value): value is string => Boolean(value)),
263
260
  };
264
261
  }
@@ -326,7 +323,7 @@ export function formatSkillResult(
326
323
  lines.push('', 'Dry run only. Re-run with --yes to apply.');
327
324
  if (result.mcpCommands.length) lines.push('', 'MCP registration:', ...result.mcpCommands);
328
325
  else if (operation === 'install') {
329
- lines.push('', 'Tip: add --actor-id <agent-id> to print MCP registration commands.');
326
+ lines.push('', 'Tip: add --agent <agent-id> to print MCP registration commands.');
330
327
  }
331
328
  return lines.join('\n');
332
329
  }