@wirecat/cli-core 0.581.0 → 0.582.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.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,21 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
5
5
  minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
6
6
  shape of this file.
7
7
 
8
- ## Unreleased
8
+ ## 0.582.0 — 05.11.2026
9
+
10
+ ### Added
11
+
12
+ - **`knowledge.updateOrganization(organization, { name })` renames an organization**, given as `organization:<id>` or
13
+ by its id; its kind and scope stay. An agent's rename is a proposal for the owner: the `organization_rename`
14
+ proposal kind and `proposeOrganizationRename(store, { organization, name }, by, reason?)`. It shows in `review` as
15
+ an `organization` row (`rename organization:3 to ...`); confirming it renames, rejecting it changes nothing. No
16
+ schema change.
17
+
18
+ ### Fixed
19
+
20
+ - **`knowledge.organizations` answers a `name:` term.** `ORGANIZATIONS_QUERY` listed `name`, but the store had no
21
+ column for it, so `organizations list name:acme` failed. The match ignores case in any script, and `name:*acme*`
22
+ finds a name that holds a word.
9
23
 
10
24
  ## 0.581.0 — 05.11.2026
11
25
 
@@ -188,7 +188,7 @@ export { type PackageUpgradeOutcome, type PackageUpgradePorts, type PackageUpgra
188
188
  export { type PersonMeetingContext, type PersonMeetingContextItem, type PersonMeetingContextOptions, type PersonMeetingReadStore, personMeetingContext, } from "./person-meeting-context.js";
189
189
  export { privatePeopleService } from "./private-people.js";
190
190
  export { approveAsTask, type ProposalTaskStore, proposalSource } from "./proposal-tasks.js";
191
- export { type Decided, type Decision, emptyProposalRegistry, FACT_SUMMARIES, ORGANIZATION_ADD_KIND, type OrganizationProposal, type ProposalFilter, type ProposalKind, type ProposalRegistry, type ProposalRow, parseProposalIds, proposalRegistry, proposeOrganization, } from "./proposals/index.js";
191
+ export { type Decided, type Decision, emptyProposalRegistry, FACT_SUMMARIES, ORGANIZATION_ADD_KIND, ORGANIZATION_RENAME_KIND, type OrganizationProposal, type OrganizationRenameProposal, type ProposalFilter, type ProposalKind, type ProposalRegistry, type ProposalRow, parseProposalIds, proposalRegistry, proposeOrganization, proposeOrganizationRename, } from "./proposals/index.js";
192
192
  export type { RetentionQuery, RetentionService } from "./retention.js";
193
193
  export { retentionService } from "./retention.js";
194
194
  export { RESOURCES_SEARCHED, type SearchAllFound, type SearchAllItem, type SearchAllMessenger, type SearchAllRequest, type SearchedResource, searchAll, } from "./search-all.js";
@@ -146,7 +146,7 @@ export { upgradePackage, } from "./package-upgrade.js";
146
146
  export { personMeetingContext, } from "./person-meeting-context.js";
147
147
  export { privatePeopleService } from "./private-people.js";
148
148
  export { approveAsTask, proposalSource } from "./proposal-tasks.js";
149
- export { emptyProposalRegistry, FACT_SUMMARIES, ORGANIZATION_ADD_KIND, parseProposalIds, proposalRegistry, proposeOrganization, } from "./proposals/index.js";
149
+ export { emptyProposalRegistry, FACT_SUMMARIES, ORGANIZATION_ADD_KIND, ORGANIZATION_RENAME_KIND, parseProposalIds, proposalRegistry, proposeOrganization, proposeOrganizationRename, } from "./proposals/index.js";
150
150
  export { retentionService } from "./retention.js";
151
151
  export { RESOURCES_SEARCHED, searchAll, } from "./search-all.js";
152
152
  export { RESOURCES_SEARCHED_WITH_MEETINGS, searchAllIncludingMeetings, searchAllWithMeetings, } from "./search-all-meetings.js";
@@ -1,5 +1,5 @@
1
1
  export { parseProposalIds } from "./ids.js";
2
2
  export { FACT_SUMMARIES } from "./kinds/findings.js";
3
- export { ORGANIZATION_ADD_KIND, type OrganizationProposal, proposeOrganization } from "./kinds/organization.js";
3
+ export { ORGANIZATION_ADD_KIND, ORGANIZATION_RENAME_KIND, type OrganizationProposal, type OrganizationRenameProposal, proposeOrganization, proposeOrganizationRename, } from "./kinds/organization.js";
4
4
  export { emptyProposalRegistry, proposalRegistry } from "./registry.js";
5
5
  export type { Decided, Decision, ProposalFilter, ProposalKind, ProposalRegistry, ProposalRow } from "./types.js";
@@ -1,4 +1,4 @@
1
1
  export { parseProposalIds } from "./ids.js";
2
2
  export { FACT_SUMMARIES } from "./kinds/findings.js";
3
- export { ORGANIZATION_ADD_KIND, proposeOrganization } from "./kinds/organization.js";
3
+ export { ORGANIZATION_ADD_KIND, ORGANIZATION_RENAME_KIND, proposeOrganization, proposeOrganizationRename, } from "./kinds/organization.js";
4
4
  export { emptyProposalRegistry, proposalRegistry } from "./registry.js";
@@ -1,11 +1,18 @@
1
1
  import type { Author, MessageStore, Organization, ProposedAction } from "../../../store/index.js";
2
2
  import type { ProposalKind } from "../types.js";
3
3
  export declare const ORGANIZATION_ADD_KIND = "organization_add";
4
+ export declare const ORGANIZATION_RENAME_KIND = "organization_rename";
4
5
  export interface OrganizationProposal {
5
6
  name: string;
6
7
  kind: Organization["kind"];
7
8
  scope?: Organization["scope"];
8
9
  }
10
+ export interface OrganizationRenameProposal {
11
+ organization: string;
12
+ name: string;
13
+ }
9
14
  /** An agent's new organization waits as a stored proposal; the organization exists only once it is confirmed. */
10
15
  export declare const proposeOrganization: (store: Pick<MessageStore, "proposedActions">, input: OrganizationProposal, by: Author, reason?: string) => Promise<ProposedAction>;
16
+ /** An agent's rename waits as a stored proposal too; the organization keeps its name until it is confirmed. */
17
+ export declare const proposeOrganizationRename: (store: Pick<MessageStore, "proposedActions">, input: OrganizationRenameProposal, by: Author, reason?: string) => Promise<ProposedAction>;
11
18
  export declare const organizationKind: (store: Pick<MessageStore, "proposedActions" | "knowledge">) => ProposalKind;
@@ -1,16 +1,19 @@
1
1
  import { proposerOfAction } from "../../../store/index.js";
2
2
  import { actionStatus, kept, pushed, storedStatus } from "./shared.js";
3
3
  export const ORGANIZATION_ADD_KIND = "organization_add";
4
+ export const ORGANIZATION_RENAME_KIND = "organization_rename";
4
5
  const rowOf = (action) => {
6
+ const renamed = action.kind === ORGANIZATION_RENAME_KIND;
5
7
  const { name, kind } = action.payload;
8
+ const { organization } = action.payload;
6
9
  return {
7
10
  id: action.id,
8
11
  kind: "organization",
9
12
  status: actionStatus(action.status),
10
13
  project: null,
11
- subject: `organization:new`,
14
+ subject: renamed ? organization : "organization:new",
12
15
  object: null,
13
- summary: `add ${kind} ${name}`,
16
+ summary: renamed ? `rename ${organization} to ${name}` : `add ${kind} ${name}`,
14
17
  proposedBy: proposerOfAction(action),
15
18
  reason: action.reason,
16
19
  evidence: [],
@@ -27,13 +30,20 @@ export const proposeOrganization = (store, input, by, reason) => store.proposedA
27
30
  payload: input,
28
31
  ...(reason === undefined ? {} : { reason }),
29
32
  });
33
+ /** An agent's rename waits as a stored proposal too; the organization keeps its name until it is confirmed. */
34
+ export const proposeOrganizationRename = (store, input, by, reason) => store.proposedActions.propose({
35
+ kind: ORGANIZATION_RENAME_KIND,
36
+ by,
37
+ payload: input,
38
+ ...(reason === undefined ? {} : { reason }),
39
+ });
30
40
  export const organizationKind = (store) => ({
31
41
  kind: "organization",
32
- stored: [ORGANIZATION_ADD_KIND],
42
+ stored: [ORGANIZATION_ADD_KIND, ORGANIZATION_RENAME_KIND],
33
43
  list: async (filter) => {
34
44
  const status = storedStatus(filter);
35
45
  const actions = await store.proposedActions.list({
36
- kinds: [ORGANIZATION_ADD_KIND],
46
+ kinds: [ORGANIZATION_ADD_KIND, ORGANIZATION_RENAME_KIND],
37
47
  ...(status === undefined ? {} : { status }),
38
48
  ...pushed(filter),
39
49
  });
@@ -45,7 +55,14 @@ export const organizationKind = (store) => ({
45
55
  await store.proposedActions.reject(id, by, reason);
46
56
  return { id, kind: "organization", status: "rejected", made: [] };
47
57
  }
48
- const { name, kind, scope } = (await store.proposedActions.get(id)).payload;
58
+ const action = await store.proposedActions.get(id);
59
+ if (action.kind === ORGANIZATION_RENAME_KIND) {
60
+ const { organization, name } = action.payload;
61
+ const renamed = await store.knowledge.updateOrganization(organization, { name });
62
+ await store.proposedActions.approve(id, by, { organization: renamed.ref });
63
+ return { id, kind: "organization", status: "confirmed", made: [] };
64
+ }
65
+ const { name, kind, scope } = action.payload;
49
66
  const made = await store.knowledge.addOrganization({ name, kind, ...(scope === undefined ? {} : { scope }) });
50
67
  await store.proposedActions.approve(id, by, { organization: made.ref });
51
68
  return { id, kind: "organization", status: "confirmed", made: [made.ref] };
@@ -6,7 +6,13 @@ export const ORGANIZATIONS_QUERY = {
6
6
  backend: "sql",
7
7
  fields: queryFieldsOf(organization, {
8
8
  native: ["id", "kind", "scope", "created_at"],
9
- examples: { id: "id:1", kind: "kind:company", scope: "scope:work", created_at: "created_at>=2026-01-01" },
9
+ examples: {
10
+ id: "id:1",
11
+ kind: "kind:company",
12
+ name: "name:*acme*",
13
+ scope: "scope:work",
14
+ created_at: "created_at>=2026-01-01",
15
+ },
10
16
  }),
11
17
  };
12
18
  export const PROJECTS_QUERY = {
@@ -5,6 +5,7 @@ import { type MeetingProjectsStore } from "./meeting-projects.js";
5
5
  import type { Author } from "./memories.js";
6
6
  import { type Link } from "./notes.js";
7
7
  import type { StoreContext } from "./open.js";
8
+ import { type OrganizationsStore } from "./organizations.js";
8
9
  import type { ProposedAction } from "./proposed-actions.js";
9
10
  import { type KnowledgeRelation, type LinkDecision, type RelateInput, type RelationUpdate, type Scored } from "./relation-proposals.js";
10
11
  import type { TagCreationOptions } from "./tag-creation.js";
@@ -100,7 +101,7 @@ export interface Reminder {
100
101
  receipt: string | null;
101
102
  }
102
103
  export type LabelledType = "note" | "document" | "person" | "organization" | "project" | "task" | "meeting" | "folder";
103
- export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
104
+ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore, OrganizationsStore {
104
105
  taskIds(key: AccountKey, options?: {
105
106
  state?: "open" | "done" | "dismissed";
106
107
  limit?: number;
@@ -159,7 +160,6 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
159
160
  name: string;
160
161
  scope?: Organization["scope"];
161
162
  }): Promise<Organization>;
162
- organizations(query?: QueryAst): Promise<Organization[]>;
163
163
  /** A project; its key is made from the name when none is given. */
164
164
  addProject(input: {
165
165
  name: string;
@@ -14,6 +14,7 @@ import { linkKind, RELATION_KINDS } from "./link-kinds.js";
14
14
  import { meetingProjectsStoreOver } from "./meeting-projects.js";
15
15
  import { holdingText } from "./note-index.js";
16
16
  import { insertLink, linkOf } from "./notes.js";
17
+ import { organizationOf, organizationsStoreOver } from "./organizations.js";
17
18
  import { queryFilter } from "./query-filter.js";
18
19
  import { relationOf, relationProposalsOver, } from "./relation-proposals.js";
19
20
  import { addTags as addThingTags, canonicalTagNamed, createTag, labelsOf, liveTagging, removeTags as removeThingTags, setMainTopic, } from "./tags.js";
@@ -205,14 +206,6 @@ export const knowledgeStoreOver = (context) => {
205
206
  throw new CliError("not_found", "no annotation with that id");
206
207
  return found;
207
208
  };
208
- const organizationOf = (row) => ({
209
- id: String(row.id),
210
- ref: `organization:${row.id}`,
211
- kind: row.kind,
212
- name: String(row.name),
213
- scope: row.scope,
214
- createdAt: iso(row.created_at),
215
- });
216
209
  const projectOf = (row) => ({
217
210
  id: String(row.id),
218
211
  ref: `project:${row.id}`,
@@ -294,6 +287,7 @@ export const knowledgeStoreOver = (context) => {
294
287
  const tagsOf = (list) => [...new Set(list.map(canonicalTagName))];
295
288
  return {
296
289
  ...vocabularyStoreOver(context),
290
+ ...organizationsStoreOver(context),
297
291
  ...meetingProjectsStoreOver(context),
298
292
  taskIds: async (key, options = {}) => {
299
293
  const { limit, offset } = bounded(options.limit, options.offset);
@@ -445,19 +439,6 @@ export const knowledgeStoreOver = (context) => {
445
439
  .get(oneOf(kind, ORGANIZATION_KINDS, "an organization's kind"), textOf(name, 200), oneOf(scope, SCOPES, "a scope"), at, at);
446
440
  return organizationOf(row);
447
441
  },
448
- organizations: async (query) => {
449
- const where = query &&
450
- queryFilter(query, {
451
- id: { type: "kw", sql: "CAST(id AS TEXT)" },
452
- kind: { type: "enum", sql: "kind" },
453
- scope: { type: "enum", sql: "scope" },
454
- created_at: { type: "time", sql: "created_at" },
455
- });
456
- return database
457
- .prepare(`SELECT * FROM organizations WHERE deleted_at IS NULL${where ? ` AND (${where.sql})` : ""} ORDER BY name, id LIMIT 500`)
458
- .all(...(where?.params ?? []))
459
- .map(organizationOf);
460
- },
461
442
  addProject: async ({ name, key, type = "other", organization, scope = "personal", description }) => write(database, () => {
462
443
  const title = textOf(name, 200);
463
444
  const organizationId = organization === undefined
@@ -0,0 +1,14 @@
1
+ import { type QueryAst } from "@wirecat/cli-base/query";
2
+ import type { Organization } from "./knowledge.js";
3
+ import type { StoreContext } from "./open.js";
4
+ type Row = Record<string, unknown>;
5
+ export declare const organizationOf: (row: Row) => Organization;
6
+ export interface OrganizationsStore {
7
+ organizations(query?: QueryAst): Promise<Organization[]>;
8
+ /** Renames an organization given as `organization:<id>` or by its id; its kind and scope stay. */
9
+ updateOrganization(organization: string, input: {
10
+ name: string;
11
+ }): Promise<Organization>;
12
+ }
13
+ export declare const organizationsStoreOver: ({ database, now }: StoreContext) => OrganizationsStore;
14
+ export {};
@@ -0,0 +1,48 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { evaluate, walkQuery } from "@wirecat/cli-base/query";
3
+ import { ORGANIZATIONS_QUERY } from "../knowledge-query.js";
4
+ import { queryFilter } from "./query-filter.js";
5
+ import { thingOf } from "./things.js";
6
+ import { write } from "./transaction.js";
7
+ export const organizationOf = (row) => ({
8
+ id: String(row.id),
9
+ ref: `organization:${row.id}`,
10
+ kind: row.kind,
11
+ name: String(row.name),
12
+ scope: row.scope,
13
+ createdAt: new Date(Number(row.created_at)).toISOString(),
14
+ });
15
+ export const organizationsStoreOver = ({ database, now }) => ({
16
+ organizations: async (query) => {
17
+ // `lower()` folds ASCII only and an organization has no folded copy, so a name filter runs on the rows.
18
+ if (query && walkQuery(query.root).some((node) => node.field === "name"))
19
+ return database.prepare("SELECT * FROM organizations WHERE deleted_at IS NULL ORDER BY name, id").all()
20
+ .map(organizationOf)
21
+ .filter((one) => evaluate(query, ORGANIZATIONS_QUERY.fields, { ...one, created_at: one.createdAt }))
22
+ .slice(0, 500);
23
+ const where = query &&
24
+ queryFilter(query, {
25
+ id: { type: "kw", sql: "CAST(id AS TEXT)" },
26
+ kind: { type: "enum", sql: "kind" },
27
+ scope: { type: "enum", sql: "scope" },
28
+ created_at: { type: "time", sql: "created_at" },
29
+ });
30
+ return database
31
+ .prepare(`SELECT * FROM organizations WHERE deleted_at IS NULL${where ? ` AND (${where.sql})` : ""} ORDER BY name, id LIMIT 500`)
32
+ .all(...(where?.params ?? []))
33
+ .map(organizationOf);
34
+ },
35
+ updateOrganization: async (organization, { name }) => write(database, () => {
36
+ const title = name.trim();
37
+ if (!title || title.length > 200)
38
+ throw new CliError("validation_error", "text takes 1–200 characters");
39
+ const thing = thingOf(database, organization.includes(":") ? organization : `organization:${organization}`);
40
+ const row = thing?.type === "organization" &&
41
+ database
42
+ .prepare("UPDATE organizations SET name = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL RETURNING *")
43
+ .get(title, now(), thing.id);
44
+ if (!row)
45
+ throw new CliError("not_found", `no organization ${organization}`);
46
+ return organizationOf(row);
47
+ }),
48
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.581.0",
3
+ "version": "0.582.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",