@wirecat/cli-core 0.548.0 → 0.549.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 (64) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/dist/calendar/cli/index.d.ts +10 -1
  3. package/dist/calendar/cli/index.js +9 -3
  4. package/dist/calendar/cli/search-all.d.ts +17 -0
  5. package/dist/calendar/cli/search-all.js +72 -0
  6. package/dist/cli/answer-entity.d.ts +4 -0
  7. package/dist/cli/answer-entity.js +5 -0
  8. package/dist/cli/bot/context.d.ts +2 -0
  9. package/dist/cli/compact.d.ts +41 -0
  10. package/dist/cli/compact.js +313 -0
  11. package/dist/cli/context.d.ts +11 -2
  12. package/dist/cli/context.js +15 -6
  13. package/dist/cli/envelope.d.ts +5 -1
  14. package/dist/cli/envelope.js +15 -4
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +2 -0
  17. package/dist/cli/messenger/search-command.d.ts +10 -0
  18. package/dist/cli/messenger/search-command.js +19 -13
  19. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  20. package/dist/cli/program.d.ts +3 -0
  21. package/dist/cli/program.js +7 -1
  22. package/dist/domain/providers.d.ts +3 -0
  23. package/dist/domain/providers.js +20 -4
  24. package/dist/domain/ref-arguments.js +3 -7
  25. package/dist/domain/ref-summaries.d.ts +56 -0
  26. package/dist/domain/ref-summaries.js +63 -0
  27. package/dist/entities/index.d.ts +11 -0
  28. package/dist/entities/relationship.d.ts +2 -0
  29. package/dist/entities/relationship.js +2 -0
  30. package/dist/entities/task-results.d.ts +10 -0
  31. package/dist/entities/task-results.js +1 -0
  32. package/dist/errors/reasons.js +1 -0
  33. package/dist/output-detail.js +9 -5
  34. package/dist/output.d.ts +5 -1
  35. package/dist/output.js +29 -4
  36. package/dist/services/proposals/kinds/relationship-update.d.ts +4 -0
  37. package/dist/services/proposals/kinds/relationship-update.js +53 -0
  38. package/dist/services/proposals/registry.js +2 -0
  39. package/dist/store/decisions.d.ts +3 -1
  40. package/dist/store/decisions.js +7 -1
  41. package/dist/store/index.d.ts +2 -1
  42. package/dist/store/sqlite/evidence-sources.d.ts +2 -2
  43. package/dist/store/sqlite/evidence-sources.js +41 -77
  44. package/dist/store/sqlite/knowledge.d.ts +17 -23
  45. package/dist/store/sqlite/knowledge.js +13 -13
  46. package/dist/store/sqlite/manifest.js +2 -0
  47. package/dist/store/sqlite/migrations.generated.js +12 -0
  48. package/dist/store/sqlite/notes.d.ts +5 -0
  49. package/dist/store/sqlite/notes.js +6 -2
  50. package/dist/store/sqlite/open-store.js +2 -0
  51. package/dist/store/sqlite/proposed-actions.d.ts +1 -1
  52. package/dist/store/sqlite/proposed-actions.js +10 -2
  53. package/dist/store/sqlite/ref-summaries.d.ts +17 -0
  54. package/dist/store/sqlite/ref-summaries.js +378 -0
  55. package/dist/store/sqlite/relation-proposals.d.ts +40 -0
  56. package/dist/store/sqlite/relation-proposals.js +88 -1
  57. package/dist/store/sqlite/schema.d.ts +16 -0
  58. package/dist/store/sqlite/schema.js +1 -0
  59. package/dist/store/sqlite/suggested-links.d.ts +10 -0
  60. package/dist/store/sqlite/suggested-links.js +68 -0
  61. package/dist/store/store.d.ts +3 -0
  62. package/dist/store/store.js +5 -0
  63. package/package.json +3 -3
  64. package/parity.json +4 -0
@@ -2,6 +2,7 @@ import { join } from "node:path";
2
2
  import { CliError, errorObjectOf, errorRecordOf, exitCodeFor, exitCodeOfError, processStreams, renderError, resolvePaths, } from "@wirecat/cli-base";
3
3
  import { metaOf, refuseJoinedPath } from "@wirecat/cli-base/commands";
4
4
  import { skillHint } from "@wirecat/cli-base/skill";
5
+ import { COMPACT_FLAG } from "@wirecat/cli-base/standards";
5
6
  import { Command, Help } from "commander";
6
7
  import { relayCommand } from "../background/relay.js";
7
8
  import { failingNotice, scheduledRuns } from "../background/schedules.js";
@@ -17,6 +18,7 @@ import { liftOptionsAfterDashes } from "./after-dashes.js";
17
18
  import { logParsedArgv, logVersion } from "./argv-log.js";
18
19
  import { commandAvailability } from "./command-availability.js";
19
20
  import { commandPathOf } from "./command-contract.js";
21
+ import { entityRows } from "./compact.js";
20
22
  import { outputFor, provide } from "./context.js";
21
23
  import { refuseByCommandPermissions, refuseWriteBeforeConnecting } from "./early-refusal.js";
22
24
  import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
@@ -73,6 +75,7 @@ export const createProgram = (definition, { out, err } = {}) => {
73
75
  .option("--max-output-bytes <bytes>", "maximum machine output bytes (default: 4194304; 0 disables)")
74
76
  .option("--brief", `${found} as reference, title, time, ${messenger ? "sender" : "author"} and excerpt only`)
75
77
  .option("--full", `${found} with their whole text and every provider field`)
78
+ .option(COMPACT_FLAG, "one line per item for an agent: reference first, text last, paging and next steps as # lines")
76
79
  .option("--dry-run", "preview parsed arguments and permissions before running the action")
77
80
  .option("--yes", "go ahead without the question an ask level puts before a write")
78
81
  .option("--record", "keep this run even where the config says record false; record_input false leaves the typed words out")
@@ -82,6 +85,8 @@ export const createProgram = (definition, { out, err } = {}) => {
82
85
  program.addCommand(command);
83
86
  configure?.(program);
84
87
  if (!contractsChecked.has(definition)) {
88
+ if (definition.rows)
89
+ entityRows(definition.rows);
85
90
  assertSharedContracts(program);
86
91
  contractsChecked.add(definition);
87
92
  }
@@ -136,7 +141,7 @@ export const run = async (argv, definition, options = {}) => {
136
141
  useAsProcessRun(outer);
137
142
  }
138
143
  };
139
- const isMachine = (words) => words.some((word) => word === "--json" || word.startsWith("--json=") || word === "--jsonl");
144
+ const isMachine = (words) => words.some((word) => word === "--json" || word.startsWith("--json=") || word === "--jsonl" || word === COMPACT_FLAG);
140
145
  const runOnce = async (argv, definition, options) => {
141
146
  const streams = options.streams ?? processStreams;
142
147
  if (!options.streams)
@@ -218,6 +223,7 @@ const runOnce = async (argv, definition, options) => {
218
223
  commandSignal: control.signal,
219
224
  trackCloseable: control.trackCloseable,
220
225
  app: definition.app,
226
+ ...(definition.rows ? { rows: definition.rows } : {}),
221
227
  platform: options.platform ?? process.platform,
222
228
  exit,
223
229
  runCommand: (words, given) => run(words, definition, given),
@@ -1,4 +1,5 @@
1
1
  import type { Provider } from "./models.js";
2
+ import { type RefKindName } from "./references.js";
2
3
  /** What a search reads; the same names `--only`, `searched` and `skipped` use. */
3
4
  export declare const KINDS: readonly ["messages", "mail", "notes", "documents", "meetings", "events"];
4
5
  export type Kind = (typeof KINDS)[number];
@@ -8,6 +9,8 @@ export interface ProviderEntry {
8
9
  aliases: readonly string[];
9
10
  /** The CLI that wrote the entry (`zm`, `drive`). One CLI may write several providers, so it is not an alias. */
10
11
  command?: string;
12
+ /** The read command, after `<command> <profile>`, that opens a ref of each kind: `{ chat: "messages list" }`. */
13
+ opens?: Partial<Record<RefKindName, string>>;
11
14
  }
12
15
  /** The seed: what cli-core knows before any CLI has written its entry into the store. */
13
16
  export declare const PROVIDERS: readonly ProviderEntry[];
@@ -1,13 +1,15 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
+ import { REF_KINDS } from "./references.js";
2
3
  /** What a search reads; the same names `--only`, `searched` and `skipped` use. */
3
4
  export const KINDS = ["messages", "mail", "notes", "documents", "meetings", "events"];
5
+ const MESSENGER_OPENS = { chat: "messages list", message: "messages show", contact: "contacts show" };
4
6
  /** The seed: what cli-core knows before any CLI has written its entry into the store. */
5
7
  export const PROVIDERS = [
6
- { name: "telegram", kind: "messages", aliases: ["tg"], command: "tg" },
8
+ { name: "telegram", kind: "messages", aliases: ["tg"], command: "tg", opens: MESSENGER_OPENS },
7
9
  { name: "telegram-bot", kind: "messages", aliases: ["tg-bot"] },
8
- { name: "max", kind: "messages", aliases: [], command: "max" },
10
+ { name: "max", kind: "messages", aliases: [], command: "max", opens: MESSENGER_OPENS },
9
11
  { name: "max-bot", kind: "messages", aliases: [] },
10
- { name: "whatsapp", kind: "messages", aliases: ["wa"], command: "wa" },
12
+ { name: "whatsapp", kind: "messages", aliases: ["wa"], command: "wa", opens: MESSENGER_OPENS },
11
13
  { name: "whatsapp-bot", kind: "messages", aliases: ["wa-bot"] },
12
14
  { name: "email", kind: "mail", aliases: [] },
13
15
  { name: "notes", kind: "notes", aliases: [] },
@@ -58,11 +60,23 @@ const invalid = (message) => {
58
60
  const wordOf = (value, what) => typeof value === "string" && WORD.test(value)
59
61
  ? value
60
62
  : invalid(`${what} is lowercase letters, digits, - and _ — not ${JSON.stringify(value)}`);
63
+ const OPEN_KINDS = new Set(REF_KINDS.map(({ type }) => type).filter((type) => type !== "entity"));
64
+ const COMMAND_WORDS = /^[a-z0-9][a-z0-9_-]*( [a-z0-9][a-z0-9_-]*)*$/;
65
+ /** A kind this build does not know is dropped, not refused: a newer CLI may open kinds added after this release. */
66
+ const opensOf = (value, named) => {
67
+ if (typeof value !== "object" || value === null || Array.isArray(value))
68
+ return invalid(`provider ${named}'s opens maps a kind of reference to a command`);
69
+ const known = Object.entries(value).filter(([kind]) => OPEN_KINDS.has(kind));
70
+ for (const [kind, command] of known)
71
+ if (typeof command !== "string" || !COMMAND_WORDS.test(command))
72
+ invalid(`provider ${named} opens a ${kind} with command words, not ${JSON.stringify(command)}`);
73
+ return Object.fromEntries(known);
74
+ };
61
75
  /** An entry as a CLI or the store gives it, checked: anything else is a `validation_error`. */
62
76
  export const providerEntryOf = (value) => {
63
77
  if (typeof value !== "object" || value === null || Array.isArray(value))
64
78
  return invalid("a provider entry is an object");
65
- const { name, kind, aliases = [], command } = value;
79
+ const { name, kind, aliases = [], command, opens } = value;
66
80
  const named = wordOf(name, "a provider's name");
67
81
  if (typeof kind !== "string" || !KINDS.includes(kind))
68
82
  invalid(`provider ${named}'s kind is one of ${KINDS.join(", ")} — not ${JSON.stringify(kind)}`);
@@ -73,6 +87,7 @@ export const providerEntryOf = (value) => {
73
87
  kind: kind,
74
88
  aliases: [...new Set(aliases.map((alias) => wordOf(alias, `an alias of ${named}`)))],
75
89
  ...(command === undefined ? {} : { command: wordOf(command, `the command of ${named}`) }),
90
+ ...(opens === undefined ? {} : { opens: opensOf(opens, named) }),
76
91
  };
77
92
  };
78
93
  const describe = ({ name, kind, command }) => `${name} (${kind}${command ? `, from ${command}` : ""})`;
@@ -87,6 +102,7 @@ const joined = (known, entry) => {
87
102
  ...same,
88
103
  aliases: [...new Set([...same.aliases, ...entry.aliases])],
89
104
  ...(entry.command ? { command: entry.command } : {}),
105
+ ...(entry.opens ? { opens: { ...same.opens, ...entry.opens } } : {}),
90
106
  }
91
107
  : entry;
92
108
  const others = known.filter((one) => one !== same);
@@ -5,18 +5,14 @@ const TAKES = {
5
5
  chat: "a chat: chat:<provider>/<account>/<chat>, its id or its title",
6
6
  message: "a message: msg:<provider>/<account>/<chat>/<message>, or a chat and the message's id",
7
7
  };
8
- const OPENS = {
9
- chat: "messages list",
10
- message: "messages show",
11
- contact: "contacts show",
12
- };
13
8
  const article = (word) => (/^[aeiou]/.test(word) ? "an" : "a");
14
9
  const wrongKind = (text, kind, takes) => new CliError("validation_error", `${text} names ${article(kind)} ${kind}; this takes ${TAKES[takes]}`, {
15
10
  reason: "ref.wrong-kind",
16
11
  });
17
12
  const wrongProvider = (text, provider, kind) => {
18
- const command = providerNamed(provider)?.command;
19
- const opens = OPENS[kind];
13
+ const entry = providerNamed(provider);
14
+ const command = entry?.command;
15
+ const opens = kind === "entity" ? undefined : entry?.opens?.[kind];
20
16
  const how = command && opens ? `open it with ${command} <profile> ${opens} ${text}` : "open it with its own CLI";
21
17
  const label = providerLabel(provider);
22
18
  return new CliError("validation_error", `${text} is ${article(label.toLowerCase())} ${label} ${kind}; ${how}`, {
@@ -0,0 +1,56 @@
1
+ import type { RefKindName } from "./references.js";
2
+ export type RefState = "available" | "deleted" | "unavailable";
3
+ /** A message or chat of the `email` provider is mail, so it reads as `email` and `email_thread`. */
4
+ export type RefSummaryKind = RefKindName | "email" | "email_thread" | "unknown";
5
+ /** What a reference names, read from the store in one batch so a page of refs costs one query per kind. */
6
+ export interface RefSummary {
7
+ /** Canonical spelling; the map key is the text the caller passed. */
8
+ ref: string;
9
+ kind: RefSummaryKind;
10
+ /** One line, at most 120 characters, never a message body. `null` when the store has no title. */
11
+ title: string | null;
12
+ /** What holds it, one line: a message's chat title, a task's project, a document's provider label. */
13
+ in: string | null;
14
+ /** ISO 8601 UTC. */
15
+ at: string | null;
16
+ provider: string | null;
17
+ /** The external id of the account that holds it, for `ProfilesFor`; `null` when it belongs to no account. */
18
+ account: string | null;
19
+ /** The CLI that opens it (`max`, `tg`, `drive`); `null` when no CLI has registered the provider. */
20
+ tool: string | null;
21
+ state: RefState;
22
+ /** Only with `{ excerpt: true }`: at most 280 characters of the body. */
23
+ excerpt?: string | null;
24
+ }
25
+ export interface RefsStore {
26
+ /** One query per kind present. Never throws for a bad ref: it comes back `unknown`, `unavailable`. */
27
+ summaries(refs: readonly string[], options?: {
28
+ excerpt?: boolean;
29
+ }): Promise<Map<string, RefSummary>>;
30
+ }
31
+ /** The caller's answer to "which profile opens this": one signed in, or the choices and why there is not one. */
32
+ export type ProfilesFor = (provider: string, account: string | null) => {
33
+ profile: string;
34
+ } | {
35
+ why: string;
36
+ profiles: string[];
37
+ };
38
+ export interface RefOpen {
39
+ /** Ready to run; contains `<profile>` when `profiles` is not exactly one. `null` when no CLI opens it. */
40
+ argv: string[] | null;
41
+ profiles: string[];
42
+ why: string | null;
43
+ }
44
+ export declare const PROFILE_PLACEHOLDER = "<profile>";
45
+ /** A title from someone else's text, as one line: a newline in it would break every row it is printed in. */
46
+ export declare const oneLine: (text: string) => string;
47
+ /**
48
+ * The command that opens a ref. A registered provider opens its own kinds; anything else opens with the caller's
49
+ * `local` words (memo's `notes show`). The profile is never picked: with none or several, `argv` keeps `<profile>`.
50
+ */
51
+ export declare const openOf: (summary: RefSummary, ports: {
52
+ profilesFor: ProfilesFor;
53
+ local?: Partial<Record<RefKindName, string[]>>;
54
+ }) => RefOpen;
55
+ /** `Chat «Сайт + автоматизация» (MAX)`: the one label every review row, status line and `show` prints. */
56
+ export declare const refLabel: (summary: RefSummary) => string;
@@ -0,0 +1,63 @@
1
+ import { singleLine } from "@wirecat/cli-base";
2
+ import { providerNamed } from "./providers.js";
3
+ export const PROFILE_PLACEHOLDER = "<profile>";
4
+ /** A title from someone else's text, as one line: a newline in it would break every row it is printed in. */
5
+ export const oneLine = (text) => singleLine(text.replace(/\s+/g, " ").trim());
6
+ /** Mail is a message and a chat to the provider registry: `opens` is keyed by the reference kind. */
7
+ const openKind = (kind) => kind === "email" ? "message" : kind === "email_thread" ? "chat" : kind === "unknown" ? undefined : kind;
8
+ /**
9
+ * The command that opens a ref. A registered provider opens its own kinds; anything else opens with the caller's
10
+ * `local` words (memo's `notes show`). The profile is never picked: with none or several, `argv` keeps `<profile>`.
11
+ */
12
+ export const openOf = (summary, ports) => {
13
+ const kind = openKind(summary.kind);
14
+ if (kind === undefined)
15
+ return { argv: null, profiles: [], why: `${summary.ref} is not a reference` };
16
+ const entry = summary.provider === null ? undefined : providerNamed(summary.provider);
17
+ const words = entry?.command === undefined ? undefined : entry.opens?.[kind];
18
+ if (entry?.command !== undefined && words !== undefined && summary.provider !== null) {
19
+ const command = [...words.split(" "), summary.ref];
20
+ const asked = ports.profilesFor(summary.provider, summary.account);
21
+ return "profile" in asked
22
+ ? { argv: [entry.command, asked.profile, ...command], profiles: [asked.profile], why: null }
23
+ : { argv: [entry.command, PROFILE_PLACEHOLDER, ...command], profiles: asked.profiles, why: asked.why };
24
+ }
25
+ const local = ports.local?.[kind];
26
+ if (local !== undefined)
27
+ return { argv: [...local, summary.ref], profiles: [], why: null };
28
+ return {
29
+ argv: null,
30
+ profiles: [],
31
+ why: summary.provider === null
32
+ ? `nothing here opens a ${kind}`
33
+ : `no CLI has registered ${summary.provider} in this store yet; run its CLI once`,
34
+ };
35
+ };
36
+ const WORDS = {
37
+ message: "Message",
38
+ chat: "Chat",
39
+ contact: "Contact",
40
+ folder: "Folder",
41
+ meeting: "Meeting",
42
+ event: "Event",
43
+ note: "Note",
44
+ document: "Document",
45
+ person: "Person",
46
+ organization: "Organization",
47
+ project: "Project",
48
+ task: "Task",
49
+ memory: "Memory",
50
+ fact: "Fact",
51
+ bot: "Bot",
52
+ email: "Email",
53
+ email_thread: "Email thread",
54
+ unknown: "Unknown",
55
+ };
56
+ /** `Chat «Сайт + автоматизация» (MAX)`: the one label every review row, status line and `show` prints. */
57
+ export const refLabel = (summary) => {
58
+ const named = summary.title === null ? summary.ref : `«${oneLine(summary.title)}»`;
59
+ const notes = [summary.in === null ? null : oneLine(summary.in), summary.state === "available" ? null : summary.state]
60
+ .filter((one) => one !== null)
61
+ .join(", ");
62
+ return `${WORDS[summary.kind]} ${named}${notes === "" ? "" : ` (${notes})`}`;
63
+ };
@@ -4911,6 +4911,7 @@ export declare const ENTITIES: {
4911
4911
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
4912
4912
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4913
4913
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4914
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4914
4915
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4915
4916
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4916
4917
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -4925,6 +4926,7 @@ export declare const ENTITIES: {
4925
4926
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
4926
4927
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4927
4928
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4929
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4928
4930
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4929
4931
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
4930
4932
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5023,6 +5025,7 @@ export declare const ENTITIES: {
5023
5025
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5024
5026
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5025
5027
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5028
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5026
5029
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5027
5030
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5028
5031
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5037,6 +5040,7 @@ export declare const ENTITIES: {
5037
5040
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5038
5041
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5039
5042
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5043
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5040
5044
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5041
5045
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5042
5046
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5136,6 +5140,7 @@ export declare const ENTITIES: {
5136
5140
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5137
5141
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5138
5142
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5143
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5139
5144
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5140
5145
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5141
5146
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5150,6 +5155,7 @@ export declare const ENTITIES: {
5150
5155
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5151
5156
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5152
5157
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5158
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5153
5159
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5154
5160
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5155
5161
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5277,6 +5283,7 @@ export declare const ENTITIES: {
5277
5283
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5278
5284
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5279
5285
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5286
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5280
5287
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5281
5288
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5282
5289
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5291,6 +5298,7 @@ export declare const ENTITIES: {
5291
5298
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5292
5299
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5293
5300
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5301
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5294
5302
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5295
5303
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5296
5304
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5383,6 +5391,7 @@ export declare const ENTITIES: {
5383
5391
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5384
5392
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5385
5393
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5394
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5386
5395
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5387
5396
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5388
5397
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -5397,6 +5406,7 @@ export declare const ENTITIES: {
5397
5406
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
5398
5407
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5399
5408
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5409
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5400
5410
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5401
5411
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
5402
5412
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -6600,6 +6610,7 @@ export declare const ENTITIES: {
6600
6610
  readonly to: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
6601
6611
  readonly kind: v.PicklistSchema<readonly ["member-of", "related-to"], undefined>;
6602
6612
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6613
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6603
6614
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6604
6615
  readonly confirmed: v.BooleanSchema<undefined>;
6605
6616
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
@@ -6,6 +6,8 @@ export declare const relationship: v.StrictObjectSchema<{
6
6
  readonly to: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
7
7
  readonly kind: v.PicklistSchema<readonly ["member-of", "related-to"], undefined>;
8
8
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
9
+ /** A purpose from memo's config; `null` means `work`. */
10
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
9
11
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10
12
  /** An agent's suggestion stays unconfirmed, and `weak`, until the owner confirms it. */
11
13
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -8,6 +8,8 @@ export const relationship = v.strictObject({
8
8
  to: reference,
9
9
  kind: v.picklist(RELATION_KINDS),
10
10
  role: v.nullable(text),
11
+ /** A purpose from memo's config; `null` means `work`. */
12
+ purpose: v.nullable(text),
11
13
  evidence: v.nullable(text),
12
14
  /** An agent's suggestion stays unconfirmed, and `weak`, until the owner confirms it. */
13
15
  confirmed: v.boolean(),
@@ -53,6 +53,7 @@ export declare const taskCard: v.StrictObjectSchema<{
53
53
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
54
54
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
55
55
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
56
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
56
57
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
57
58
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
58
59
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -67,6 +68,7 @@ export declare const taskCard: v.StrictObjectSchema<{
67
68
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
68
69
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
69
70
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
71
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
70
72
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
71
73
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
72
74
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -165,6 +167,7 @@ export declare const taskCreated: v.StrictObjectSchema<{
165
167
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
166
168
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
167
169
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
170
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
168
171
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
169
172
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
170
173
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -179,6 +182,7 @@ export declare const taskCreated: v.StrictObjectSchema<{
179
182
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
180
183
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
181
184
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
185
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
182
186
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
183
187
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
184
188
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -278,6 +282,7 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
278
282
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
279
283
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
280
284
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
285
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
281
286
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
282
287
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
283
288
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -292,6 +297,7 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
292
297
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
293
298
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
294
299
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
300
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
295
301
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
296
302
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
297
303
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -419,6 +425,7 @@ export declare const taskMerge: v.StrictObjectSchema<{
419
425
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
420
426
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
421
427
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
428
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
422
429
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
423
430
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
424
431
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -433,6 +440,7 @@ export declare const taskMerge: v.StrictObjectSchema<{
433
440
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
434
441
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
435
442
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
443
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
436
444
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
437
445
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
438
446
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -525,6 +533,7 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
525
533
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
526
534
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
527
535
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
536
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
528
537
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
529
538
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
530
539
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -539,6 +548,7 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
539
548
  readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
540
549
  readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
541
550
  readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
551
+ readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
542
552
  readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
543
553
  readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
544
554
  readonly confirmed: v.BooleanSchema<undefined>;
@@ -15,6 +15,7 @@ const link = v.strictObject({
15
15
  origin: v.picklist(LINK_SOURCES),
16
16
  target_text: v.nullable(text),
17
17
  role: v.nullable(text),
18
+ purpose: v.nullable(text),
18
19
  evidence: v.nullable(text),
19
20
  provenance: v.nullable(text),
20
21
  confirmed: v.boolean(),
@@ -144,6 +144,7 @@ const OTHERS = [
144
144
  ],
145
145
  validation("name.invalid", "the name is not valid"),
146
146
  validation("output.choose-fields", "at a terminal, --json names the fields it prints"),
147
+ validation("output.conflict", "two output switches ask for different output"),
147
148
  validation("output.one-object", "a report is one JSON object; --jsonl streams lists only"),
148
149
  validation("output.unknown-field", "the answer has no field by that name"),
149
150
  validation("name.taken", "the name is already used"),
@@ -2,13 +2,17 @@ export const DETAILS = ["brief", "full"];
2
2
  /** Message fields mail never has and a messenger hit rarely fills; a null one is noise. */
3
3
  const RARELY_SET = ["editedAt", "replyTo", "forwardedFrom", "reactions"];
4
4
  const BRIEF = ["locator", "chatTitle", "timestamp", "senderName", "excerpt", "score"];
5
- const isHit = (value) => typeof value === "object" &&
6
- value !== null &&
7
- typeof value.locator === "string" &&
8
- typeof value.text === "string";
5
+ /** A messenger answer's hit (`message.hit`): already cut to an excerpt, so only `brief` changes it. */
6
+ const SNAKE_BRIEF = ["ref", "chat_title", "timestamp", "sender_name", "excerpt", "score"];
7
+ const isRecord = (value) => typeof value === "object" && value !== null;
8
+ const isSnakeHit = (value) => isRecord(value) && typeof value.ref === "string" && typeof value.excerpt === "string";
9
+ const isHit = (value) => isSnakeHit(value) || (isRecord(value) && typeof value.locator === "string" && typeof value.text === "string");
10
+ const picked = (hit, keys) => Object.fromEntries(keys.flatMap((key) => (hit[key] === undefined || hit[key] === null ? [] : [[key, hit[key]]])));
9
11
  const shaped = (hit, detail) => {
12
+ if (isSnakeHit(hit))
13
+ return detail === "brief" ? picked(hit, SNAKE_BRIEF) : hit;
10
14
  if (detail === "brief")
11
- return Object.fromEntries(BRIEF.flatMap((key) => (hit[key] === undefined || hit[key] === null ? [] : [[key, hit[key]]])));
15
+ return picked(hit, BRIEF);
12
16
  const { text, providerMetadata, ...rest } = hit;
13
17
  for (const key of RARELY_SET)
14
18
  if (rest[key] === null)
package/dist/output.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface OutputOptions {
14
14
  hitDetail?: Detail;
15
15
  /** Takes the answer as an object in place of printing it — an agent's run (`BaseEnvironment.agent`). */
16
16
  capture?: (value: unknown) => void;
17
+ /** One line per item (WC-OUT-24); anything printed past `renderAnswer` stays JSON. */
18
+ compact?: boolean;
17
19
  }
18
20
  /**
19
21
  * One place decides the mode, so no command can disagree with another.
@@ -23,9 +25,11 @@ export interface OutputOptions {
23
25
  * instead. In every machine mode **stdout carries JSON and nothing else** — diagnostics are on stderr in all modes, which is what makes
24
26
  * that contract hold by construction rather than by remembering.
25
27
  */
26
- export declare const resolveOutput: ({ json, jsonl, quiet, streams, tty, color, fields, hitDetail, env, capture, }?: OutputOptions) => {
28
+ export declare const resolveOutput: ({ json, jsonl, quiet, streams, tty, color, fields, hitDetail, env, capture, compact, }?: OutputOptions) => {
27
29
  format: "json" | "jsonl" | "pretty";
28
30
  color: boolean;
29
31
  streams: Streams;
30
32
  renderer: Renderer;
33
+ compact?: boolean | undefined;
34
+ full?: boolean | undefined;
31
35
  };
package/dist/output.js CHANGED
@@ -1,6 +1,23 @@
1
- import { createRenderer, processStreams, singleLine, } from "@wirecat/cli-base";
1
+ import { CliError, createRenderer, processStreams, singleLine, } from "@wirecat/cli-base";
2
+ import { COMPACT_FLAG } from "@wirecat/cli-base/standards";
2
3
  import { fieldsOf, projectFields } from "./cli/result-fields.js";
3
4
  import { atDetail } from "./output-detail.js";
5
+ const refuseCompact = ({ json, jsonl, fields, hitDetail }) => {
6
+ const other = jsonl
7
+ ? "--jsonl"
8
+ : fields !== undefined
9
+ ? "--json=…"
10
+ : json
11
+ ? "--json"
12
+ : hitDetail === "brief"
13
+ ? "--brief"
14
+ : "";
15
+ if (other)
16
+ throw new CliError("validation_error", `${COMPACT_FLAG} and ${other} ask for different output — pick one`, {
17
+ reason: "output.conflict",
18
+ param: COMPACT_FLAG,
19
+ });
20
+ };
4
21
  /**
5
22
  * One place decides the mode, so no command can disagree with another.
6
23
  *
@@ -9,9 +26,11 @@ import { atDetail } from "./output-detail.js";
9
26
  * instead. In every machine mode **stdout carries JSON and nothing else** — diagnostics are on stderr in all modes, which is what makes
10
27
  * that contract hold by construction rather than by remembering.
11
28
  */
12
- export const resolveOutput = ({ json, jsonl, quiet, streams = processStreams, tty, color, fields, hitDetail, env = process.env, capture, } = {}) => {
29
+ export const resolveOutput = ({ json, jsonl, quiet, streams = processStreams, tty, color, fields, hitDetail, env = process.env, capture, compact, } = {}) => {
30
+ if (compact)
31
+ refuseCompact({ json, jsonl, fields, hitDetail });
13
32
  const interactive = tty ?? process.stdout.isTTY === true;
14
- const format = jsonl ? "jsonl" : json || !interactive ? "json" : "pretty";
33
+ const format = jsonl ? "jsonl" : json || compact || !interactive ? "json" : "pretty";
15
34
  const painted = format === "pretty" && (color ?? (env.NO_COLOR === undefined && env.TERM !== "dumb" && !env.CI));
16
35
  const printing = createRenderer({ format, color: painted, streams });
17
36
  const rendered = capture
@@ -37,7 +56,13 @@ export const resolveOutput = ({ json, jsonl, quiet, streams = processStreams, tt
37
56
  stream: (items) => created.stream([...items].map((item) => projectFields(item, paths))),
38
57
  }
39
58
  : created;
40
- return { format, color: painted, streams, renderer: quiet ? silence(renderer) : renderer };
59
+ return {
60
+ format,
61
+ color: painted,
62
+ streams,
63
+ renderer: quiet ? silence(renderer) : renderer,
64
+ ...(compact ? { compact: true, full: hitDetail === "full" } : {}),
65
+ };
41
66
  };
42
67
  /** Fields a person writes in paragraphs; every other string is a name, a title, an id or a path. */
43
68
  const MULTI_LINE = new Set(["text", "description"]);
@@ -0,0 +1,4 @@
1
+ import type { MessageStore } from "../../../store/index.js";
2
+ import type { ProposalKind } from "../types.js";
3
+ /** An agent's change to a relationship's role or purpose; the owner's applies at once and never waits here. */
4
+ export declare const relationshipUpdateKind: (store: Pick<MessageStore, "knowledge" | "proposedActions">) => ProposalKind;