connection-centric 0.2.0 → 0.3.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/GUIDE.md CHANGED
@@ -32,6 +32,7 @@ A tool lapses after 90 days unused. The person disconnects it from Me, or with `
32
32
  - **company**: the one container a firm holds for work that belongs to no site. Never archived; nobody makes one but Centric, when an Operator approves its Application.
33
33
  - **channel**: a named conversation inside a container, and the only place a container's messages live. In a Project it is either visible to externals or staff-only. A Company channel may be private: read only by the people its admins name.
34
34
  - **message**: one message in a channel or a Direct. A reply quotes the message it answers. Files sent in a channel are kept as documents.
35
+ - **pinned-message**: a message kept at the top of its channel, Direct or conversation, for everyone in it, at most 50 in one place. In a Project only staff pin; anyone who may pin may unpin. A channel's pins are on the record; a Direct's and a conversation's are not.
35
36
  - **direct**: a conversation between two people who share a container, or a person's own with themselves, for notes and what they keep to file later. Name your own `me` (`direct send me`); `direct list` marks it `you`. Nothing in a Direct is part of the record: no task, no issuance, no deleting, and the assistant never reads or corrects one.
36
37
  - **task**: an open piece of work in one container, which anyone who can see it may close or reopen. Every task is part of one channel's conversation: made on its own, it is posted there as your message; what is said about it is a reply to that message (`message send --task`). It may carry details, a due day, a priority and a zone. Open past its due day, or with an ask escalated, it is overdue.
37
38
  - **ask**: one person's request to another, on a task, closed by closing the task. The person asked may answer not yet, saying why and when to be asked again.
package/centric.mjs CHANGED
@@ -105,15 +105,15 @@ async function connect(c, a, server) {
105
105
  }
106
106
  }
107
107
  mkdirSync(dir(), { recursive: true, mode: 0o700 });
108
- writeFileSync(file(), JSON.stringify({ server, credential: got.credential, tool: got.tool, identity: got.identity }, null, 2), { mode: 0o600 });
108
+ writeFileSync(file(), JSON.stringify({ server, credential: got.credential }, null, 2), { mode: 0o600 });
109
109
  chmodSync(file(), 0o600); // a file there already keeps its old mode otherwise
110
110
  return { tool: got.tool, identity: got.identity, server };
111
111
  }
112
112
 
113
113
  // ── printing ──
114
- const clip = (s, n = 90) => { s = String(s).replace(/\s+/g, " "); return s.length > n ? `${s.slice(0, n - 1)}…` : s; };
114
+ const clip = (s) => { s = String(s).replace(/\s+/g, " "); return s.length > 70 ? `${s.slice(0, 69)}…` : s; };
115
115
  const label = (x) => x.name ?? x.title ?? x.topic_name ?? x.task_title ?? x.document_name ?? x.display_name ?? x.firm_name
116
- ?? (x.text != null ? `${x.sender?.display_name ?? "?"}${x.via ? ` (via ${x.via.name})` : ""}: ${clip(x.text, 70)}` : (x.tombstone ?? x).deleted_by_name ? `deleted by ${(x.tombstone ?? x).deleted_by_name}` : null);
116
+ ?? (x.text != null ? `${x.sender?.display_name ?? "?"}${x.via ? ` (via ${x.via.name})` : ""}: ${clip(x.text)}` : (x.tombstone ?? x).deleted_by_name ? `deleted by ${(x.tombstone ?? x).deleted_by_name}` : null);
117
117
  const where = (x) => x.container_name ?? (x.channel ? `${x.channel.name} · ${x.channel.container_name}` : x.direct ? `Direct with ${x.direct.display_name}` : null);
118
118
  // A tombstone in channel show is named by the message it stands for; a message with a file, by the document to download.
119
119
  const row = (x) => (x && typeof x === "object" ? [x.kind && `[${x.kind}]`, x.id ?? x.zulip_message_id, label(x), where(x), x.document && `document ${x.document.id}`, x.due_on && `due ${x.due_on}`, x.this && "(this one)"].filter((v) => v != null && v !== "").join(" ") : String(x));
@@ -137,12 +137,14 @@ async function main(argv) {
137
137
  let json = argv.includes("--json");
138
138
  try {
139
139
  const { cmd, given, opts, lead } = parse(argv);
140
- json = !!opts.json || json;
140
+ json ||= opts.json === true || opts.json === "true"; // --json=true too, which argv alone does not show
141
+ // Help is one JSON value too under --json (rule 9): a program reading stdout gets what it asked for, or an error.
142
+ const say = (h) => { process.stdout.write(`${json ? asJson({ help: h }) : h}\n`); return 0; };
141
143
  if (!cmd) {
142
- if (!lead.length || lead[0] === "help") { process.stdout.write(`${help(lead[0] === "help" && (commandOf(lead[1], null) ?? commandOf(lead[1], lead[2])))}\n`); return 0; }
144
+ if (!lead.length || lead[0] === "help") return say(help(lead[0] === "help" && (commandOf(lead[1], null) ?? commandOf(lead[1], lead[2]))));
143
145
  throw new Refused(`no command "${lead.slice(0, 2).join(" ")}"; centric help lists them`);
144
146
  }
145
- if (opts.help) { process.stdout.write(`${help(cmd)}\n`); return 0; }
147
+ if (opts.help) return say(help(cmd));
146
148
  const file = saved(), server = opts.server || process.env.CENTRIC_SERVER || file.server || DEFAULT_SERVER; // set but empty is not set
147
149
  // The credential crosses a network only encrypted: an http:// server is this machine's, for development.
148
150
  if (!/^https:\/\//i.test(server) && !/^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?(\/|$)/i.test(server)) throw new Refused(`${server} is not https; a credential is sent only over https, or to this machine`);
package/commands.mjs CHANGED
@@ -16,7 +16,7 @@ export const CLI_VERSION = JSON.parse(readFileSync(new URL("./package.json", imp
16
16
 
17
17
  // Rule 2: the verbs, each meaning one thing on every noun. A new verb is added here, and to the spec, before it is used.
18
18
  export const VERBS = ["list", "show", "make", "edit", "delete", "archive", "unarchive", "close", "reopen", "not-yet", "grant", "revoke", "appoint", "remove",
19
- "issue", "acknowledge", "add", "revise", "restore", "download", "messages", "mark-read", "send", "react", "unreact", "history", "readers",
19
+ "issue", "acknowledge", "add", "revise", "restore", "download", "messages", "mark-read", "send", "forward", "react", "unreact", "history", "readers",
20
20
  "pin", "unpin", "mute", "unmute", "private", "open-to-company", "set-picture", "remove-picture", "set-photo", "remove-photo",
21
21
  "approve", "reject", "withdraw", "rotate-key", "dormant", "reactivate", "report", "record-pack", "audit", "question", "correct", "disconnect"];
22
22
 
@@ -103,6 +103,7 @@ const TEXT = { kind: "text", need: true, about: "the words, or - to read them fr
103
103
  const PATH = { kind: "path", need: true, about: "a file on this computer" };
104
104
  const NOTE = { kind: "text", about: "what it is, or what changed" };
105
105
  const DRY = { kind: "bool", about: "say what would change, and change nothing" };
106
+ const SEARCH = { kind: "words" }; // only the messages holding these words (Features 523)
106
107
  const PAGE = { kind: "path", about: "write it as a page, a file ending .html" };
107
108
  // A page the server draws (a report, a record pack), written to this computer: never over a file already there.
108
109
  const page = async (c, path, out) => { if (!/\.html?$/i.test(out)) refuse("--out is a page: name it something.html"); const b = await c.bytes(path); await c.save(out, b); return { path: out, bytes: b.length }; };
@@ -130,11 +131,24 @@ export const COMMANDS = [
130
131
  ...named.items.map((x) => ({ kind: "mention", ...x })), ...directs.items.map((x) => ({ kind: "direct", ...x })),
131
132
  ]);
132
133
  } },
133
- { noun: "search", verb: null, target: { name: "words", kind: "words" }, flags: {}, about: "Channels, tasks, documents, zones, people and messages, by a word or two.",
134
+ // Search (CONTEXT.md Search, ADR 0009): the record by its words, every version of a message, the words inside a PDF, and
135
+ // your own Directs; narrowed by who, where, when, a document's kind and a state.
136
+ { noun: "search", verb: null, target: { name: "words", kind: "words", optional: true },
137
+ flags: { from: { kind: "identity", about: "only what this person did" },
138
+ in: { kind: "container" }, channel: { kind: "channel" }, task: { kind: "task", about: "only in its conversation" },
139
+ after: { kind: "day", about: "from this day" }, before: { kind: "day", about: "to this day" },
140
+ kind: { kind: "choice", of: ["photo", "pdf", "video", "voice", "spreadsheet", "other"] },
141
+ state: { kind: "choice", of: ["open", "closed", "unacknowledged"], about: "tasks open or closed; issuances not acknowledged" },
142
+ oldest: { kind: "bool", about: "oldest first, not best first" } },
143
+ about: "The record by its words: messages (earlier versions too), documents and their text, tasks, issuances, deletions, channels, people, your own Directs.",
134
144
  async run(c, a) {
135
- const [r, said] = await Promise.all([c.get(`/api/search?q=${enc(a.words)}`), c.get(`/api/messages?search=${enc(a.words)}&limit=20`)]);
136
- return listed([...r.channels.map((x) => ({ kind: "channel", ...x })), ...r.tasks.map((x) => ({ kind: "task", ...x })), ...r.documents.map((x) => ({ kind: "document", ...x })),
137
- ...r.zones.map((x) => ({ kind: "zone", ...x })), ...r.people.map((x) => ({ kind: "identity", ...x })), ...said.items.map((x) => ({ kind: "message", ...x }))]);
145
+ if (a.channel && a.task) refuse("--channel or --task, not both");
146
+ const by = { from: a.from, in: a.channel ?? a.task ?? a.in, after: a.after, before: a.before, kind: a.kind, state: a.state };
147
+ if (!a.words && !Object.values(by).some(Boolean)) refuse("search needs the words, or one of --from, --in, --channel, --task, --after, --before, --kind and --state");
148
+ const r = await c.get(`/api/search${qs({ q: a.words, ...by, order: a.oldest && "oldest" })}`), revised = new Set(r.revisions.map((x) => x.document_id));
149
+ return listed([...r.messages.map((x) => ({ kind: "message", ...x })), ...r.directs.map((x) => ({ kind: "direct", ...x })), ...r.revisions.map((x) => ({ kind: "revision", ...x })),
150
+ ...r.documents.filter((x) => !revised.has(x.id)).map((x) => ({ kind: "document", ...x })), ...r.tasks.map((x) => ({ kind: "task", ...x })), ...r.issuances.map((x) => ({ kind: "issuance", ...x })),
151
+ ...r.tombstones.map((x) => ({ kind: "tombstone", ...x })), ...r.channels.map((x) => ({ kind: "channel", ...x })), ...r.zones.map((x) => ({ kind: "zone", ...x })), ...r.people.map((x) => ({ kind: "identity", ...x }))]);
138
152
  } },
139
153
  { noun: "guide", verb: null, flags: { as: { kind: "choice", of: ["skill", "agents"], about: "as a Claude Code skill (a SKILL.md), or as a section an AGENTS.md can include" } },
140
154
  about: "The instructions for a program using this command: what each noun means, what is refused and why, the mark, the cap, --json, the exit codes, and every command.",
@@ -153,9 +167,13 @@ export const COMMANDS = [
153
167
  } },
154
168
 
155
169
  // ── work ──
156
- { noun: "task", verb: "list", flags: { in: IN, state: { kind: "choice", of: ["open", "closed"] }, zone: { kind: "zone" } },
170
+ { noun: "task", verb: "list", flags: { in: IN, state: { kind: "choice", of: ["open", "closed"] }, zone: { kind: "zone" }, before: { kind: "words", about: "with --state closed: the next page, from the more: of the one before" } },
157
171
  about: "A container's tasks: open ones first, soonest due, then the most important; done ones newest first, a page at a time.",
158
- run: async (c, a) => listed(await c.get(`/api/containers/${a.in}/tasks${qs({ state: a.state, zone_id: a.zone })}`)) },
172
+ run: async (c, a) => {
173
+ if (a.before && a.state !== "closed") refuse("--before pages done tasks: with --state closed");
174
+ const l = await c.get(`/api/containers/${a.in}/tasks${qs({ state: a.state, zone_id: a.zone, before: a.before })}`);
175
+ return listed(l, a.state === "closed" && l.length >= 100 ? l.at(-1).done_key : null); // the server's page of done ones (DONE_PAGE)
176
+ } },
159
177
  { noun: "task", verb: "show", target: { name: "task", kind: "task" }, flags: {}, about: "One task in full: its state, details, due day, priority, zone, and every ask on it with what happened since.",
160
178
  run: (c, a) => c.get(`/api/tasks/${a.task}`) },
161
179
  { noun: "ask", verb: "list", flags: { "to-me": { kind: "bool", about: "only asks of you" }, "from-me": { kind: "bool", about: "only your own asks" }, state: { kind: "choice", of: ["open", "closed"], about: "open (the default) or done" } },
@@ -163,9 +181,9 @@ export const COMMANDS = [
163
181
  run: (c, a) => (a["to-me"] && a["from-me"] ? refuse("--to-me or --from-me, not both") : c.get(`/api/asks${qs({ to_me: a["to-me"], from_me: a["from-me"], state: a.state })}`).then((x) => listed(x))) },
164
182
  { noun: "direct", verb: "list", flags: {}, about: "Your Directs with something in them, newest first.",
165
183
  run: async (c) => listed((await c.get("/api/places")).filter((p) => p.kind === "direct").sort(newest)) },
166
- { noun: "direct", verb: "messages", target: { name: "identity", kind: "identity" }, flags: { before: { kind: "message", about: "older than this message" }, limit: { kind: "int", about: "how many, up to 100 (50 if left out)" } },
184
+ { noun: "direct", verb: "messages", target: { name: "identity", kind: "identity" }, flags: { before: { kind: "message", about: "older than this message" }, limit: { kind: "int", about: "how many, up to 100 (50 if left out)" }, search: SEARCH },
167
185
  about: "A Direct's messages, newest last. Marks nothing read.",
168
- run: (c, a) => c.get(`/api/messages${qs({ direct: a.identity, before: a.before, limit: a.limit })}`) },
186
+ run: (c, a) => c.get(`/api/messages${qs({ direct: a.identity, before: a.before, limit: a.limit, search: a.search })}`) },
169
187
  { noun: "issuance", verb: "list", flags: { "to-me": { kind: "bool", about: "only what was issued to you" }, "by-me": { kind: "bool", about: "only what you issued" }, in: { kind: "container" }, state: { kind: "choice", of: ["open", "acknowledged"] } },
170
188
  about: "What was issued to you or by you, newest first.",
171
189
  run: (c, a) => (a["to-me"] && a["by-me"] ? refuse("--to-me or --by-me, not both") : c.get(`/api/issuances${qs({ to_me: a["to-me"], by_me: a["by-me"], in: a.in, state: a.state })}`).then((x) => listed(x))) },
@@ -186,21 +204,21 @@ export const COMMANDS = [
186
204
 
187
205
  // ── places in a container ──
188
206
  { noun: "channel", verb: "list", flags: { in: IN, archived: { kind: "bool", about: "the archived ones instead" } }, about: "A container's channels that you read.",
189
- run: async (c, a) => listed((await c.get(`/api/containers/${a.in}`)).channels.filter((ch) => ch.reads && !ch.archived_at === !a.archived)) },
207
+ run: async (c, a) => listed((await c.get(`/api/containers/${a.in}?documents=0`)).channels.filter((ch) => ch.reads && !ch.archived_at === !a.archived)) },
190
208
  { noun: "channel", verb: "show", target: { name: "channel", kind: "channel" }, flags: {}, about: "One channel: its brief, flag, readers if private, and tombstones.",
191
209
  run: (c, a) => c.get(`/api/channels/${a.channel}`) },
192
210
  { noun: "channel", verb: "messages", target: { name: "channel", kind: "channel" },
193
- flags: { before: { kind: "message", about: "older than this message" }, after: { kind: "message", about: "newer than this message" }, limit: { kind: "int", about: "how many, up to 100 (50 if left out)" }, unread: { kind: "bool", about: "only what you have not read" } },
211
+ flags: { before: { kind: "message", about: "older than this message" }, after: { kind: "message", about: "newer than this message" }, limit: { kind: "int", about: "how many, up to 100 (50 if left out)" }, unread: { kind: "bool", about: "only what you have not read" }, search: SEARCH },
194
212
  about: "A channel's messages, newest last, with tombstones where messages were deleted. Marks nothing read.",
195
- run: (c, a) => (a.before && a.after ? refuse("--before or --after, not both") : c.get(`/api/messages${qs({ channel: a.channel, before: a.before, after: a.after, limit: a.limit, unread: a.unread })}`)) },
213
+ run: (c, a) => (a.before && a.after ? refuse("--before or --after, not both") : c.get(`/api/messages${qs({ channel: a.channel, before: a.before, after: a.after, limit: a.limit, unread: a.unread, search: a.search })}`)) },
196
214
  { noun: "zone", verb: "list", flags: { in: { ...IN, kind: "project" } }, about: "A Project's zones.",
197
- run: async (c, a) => listed((await c.get(`/api/containers/${a.in}`)).zones) },
215
+ run: async (c, a) => listed((await c.get(`/api/containers/${a.in}?documents=0`)).zones) },
198
216
  { noun: "document", verb: "list", flags: { in: IN, folder: { kind: "folder", about: "only what is in this folder" } }, about: "A container's documents, newest revision first.",
199
- run: async (c, a) => listed((await c.get(`/api/containers/${a.in}`)).documents.filter((d) => !a.folder || d.folder_id === a.folder)) },
217
+ run: async (c, a) => listed((await c.get(`/api/containers/${a.in}/documents`)).filter((d) => !a.folder || d.folder_id === a.folder)) },
200
218
  { noun: "document", verb: "show", target: { name: "document", kind: "document" }, flags: {}, about: "One document: its revisions, issuances and acknowledgements.",
201
219
  run: (c, a) => c.get(`/api/documents/${a.document}`) },
202
220
  { noun: "folder", verb: "list", flags: { in: IN }, about: "A container's folders that you can see.",
203
- run: async (c, a) => listed((await c.get(`/api/containers/${a.in}`)).folders) },
221
+ run: async (c, a) => listed((await c.get(`/api/containers/${a.in}?documents=0`)).folders) },
204
222
 
205
223
  // ── containers, people and Centric ──
206
224
  { noun: "project", verb: "list", flags: {}, about: "The Projects you belong to.", run: async (c) => listed((await c.get("/api/me")).containers.filter((x) => x.kind === "project")) },
@@ -210,9 +228,9 @@ export const COMMANDS = [
210
228
  { noun: "company", verb: "show", target: { name: "company", kind: "company" }, flags: {}, about: "One Company in full: its brief, people, channels and documents.",
211
229
  run: (c, a) => c.get(`/api/containers/${a.company}`) },
212
230
  { noun: "membership", verb: "list", flags: { in: IN }, about: "Who belongs to a container, with each one's designation.",
213
- run: async (c, a) => listed((await c.get(`/api/containers/${a.in}`)).members.map((m) => ({ id: m.membership_id, ...m }))) },
231
+ run: async (c, a) => listed((await c.get(`/api/containers/${a.in}?documents=0`)).members.map((m) => ({ id: m.membership_id, ...m }))) },
214
232
  { noun: "admin", verb: "list", flags: { company: { kind: "company", need: true } }, about: "A Company's admins.",
215
- run: async (c, a) => listed((await c.get(`/api/containers/${a.company}`)).members.filter((m) => m.is_admin).map((m) => ({ id: m.admin_id, identity_id: m.identity_id, display_name: m.display_name, designation: m.designation }))) },
233
+ run: async (c, a) => listed((await c.get(`/api/containers/${a.company}?documents=0`)).members.filter((m) => m.is_admin).map((m) => ({ id: m.admin_id, identity_id: m.identity_id, display_name: m.display_name, designation: m.designation }))) },
216
234
  { noun: "identity", verb: "show", target: { name: "identity", kind: "identity" }, flags: {}, about: "One person: their name, and the containers you both belong to with their designation in each.",
217
235
  run: (c, a) => c.get(`/api/identities/${a.identity}`) },
218
236
  { noun: "access-link", verb: "list", flags: { in: { ...IN, kind: "project" } }, about: "A Project's access links: who each is for, and whether it was used, withdrawn or ran out.",
@@ -277,6 +295,9 @@ export const COMMANDS = [
277
295
  const uploads = a.path && (await Promise.all(a.path.map((p) => c.upload(p)))).map((u) => ({ path: u.path, name: u.name }));
278
296
  return c.post("/api/messages", { channel: a.channel, task: a.task, text: a.text, reply_to: a["reply-to"], mention: a.mention, mention_all: a["mention-all"], uploads });
279
297
  } },
298
+ { noun: "message", verb: "forward", target: { name: "message", kind: "message" }, flags: { channel: { kind: "channel" }, direct: { kind: "identity" }, "dry-run": { ...DRY, about: "who would newly read it" } },
299
+ about: "Sends a message on, marked with who wrote it and the day, never where; a file as itself.",
300
+ run: (c, a) => c.post(`/api/messages/${a.message}/forward`, { channel: a.channel, direct: a.direct, dry_run: a["dry-run"] }) },
280
301
  { noun: "message", verb: "edit", target: { name: "message", kind: "message" }, flags: { text: TEXT }, about: "Changes the words of a message you sent; Zulip keeps every earlier version.",
281
302
  run: (c, a) => c.patch(`/api/messages/${a.message}`, { text: a.text }) },
282
303
  { noun: "message", verb: "delete", target: { name: "message", kind: "message" }, flags: {}, about: "Deletes a channel message, leaving a tombstone where it stood: staff only.",
@@ -285,6 +306,18 @@ export const COMMANDS = [
285
306
  run: (c, a) => c.post(`/api/messages/${a.message}/reactions`, { emoji: a.emoji }) },
286
307
  { noun: "message", verb: "unreact", target: { name: "message", kind: "message" }, flags: { emoji: EMOJI }, about: "Takes your reaction back.",
287
308
  run: (c, a) => c.del(`/api/messages/${a.message}/reactions`, { emoji: a.emoji }) },
309
+ // A pinned message (CONTEXT.md Pinned message, #146): kept at the top of its conversation, for everyone in it. Given
310
+ // --conversation, the id is one of the assistant's answers there, as conversation show lists them.
311
+ ...["pin", "unpin"].map((verb) => ({ noun: "message", verb, target: { name: "message", kind: "message" }, flags: { conversation: { kind: "id", about: "an answer in this conversation of yours" } },
312
+ about: verb === "pin" ? "Pins a message for everyone in its conversation. In a Project, staff pin; 50 at most." : "Takes a message's pin off.",
313
+ run: (c, a) => c[verb === "pin" ? "post" : "del"]("/api/pins", a.conversation ? { conversation: a.conversation, turn: a.message } : { message: a.message }) })),
314
+ { noun: "pinned-message", verb: "list", flags: { channel: { kind: "channel" }, direct: { kind: "identity" }, conversation: { kind: "id" } },
315
+ about: "A channel's, Direct's or conversation's pinned messages, newest pinned first.",
316
+ run: async (c, a) => {
317
+ const where = ["channel", "direct", "conversation"].filter((k) => a[k]);
318
+ if (where.length !== 1) refuse("say where: --channel, --direct or --conversation");
319
+ return listed((await c.get(`/api/pins${qs({ [where[0]]: a[where[0]] })}`)).pins);
320
+ } },
288
321
  { noun: "direct", verb: "send", target: { name: "identity", kind: "identity" }, flags: { text: TEXT }, about: "Writes to someone in your Direct with them, or to yourself as me. It passes through and is kept nowhere.",
289
322
  run: (c, a) => c.post("/api/messages", { direct: a.identity, text: a.text }) },
290
323
  { noun: "channel", verb: "mark-read", target: { name: "channel", kind: "channel" }, flags: { through: { kind: "message", about: "up to and including this message" } }, about: "Read, on purpose: tells its senders their messages were read.",
@@ -433,9 +466,9 @@ export const COMMANDS = [
433
466
  { noun: "assistant", verb: "question", target: { name: "text", kind: "words" }, flags: { conversation: { kind: "id", about: "to go on from one" }, focus: { kind: "focus", about: "a Project, Company or channel for a new conversation to keep to" } },
434
467
  about: "Asks Centric's assistant, which answers from what you can see. Only while your own switch is on; it counts against the day's share.",
435
468
  run: (c, a) => c.post("/api/assist", { q: a.text, conversation: a.conversation, focus: a.focus }) },
436
- { noun: "assistant", verb: "correct", target: { name: "text", kind: "words" }, flags: { direct: { kind: "identity", about: "the person a Direct it is for: always refused, as the app never corrects one (ADR 0006)" } },
469
+ { noun: "assistant", verb: "correct", target: { name: "text", kind: "words" }, flags: {},
437
470
  about: "The assistant puts a message's spelling, grammar and punctuation right; it is still yours to send.",
438
- run: (c, a) => c.post("/api/assist/correct", { text: a.text, direct: a.direct }) },
471
+ run: (c, a) => c.post("/api/assist/correct", { text: a.text }) },
439
472
  { noun: "conversation", verb: "delete", target: { name: "conversation", kind: "id" }, flags: {}, about: "Deletes a conversation with the assistant outright; it leaves no tombstone.",
440
473
  run: (c, a) => c.del(`/api/assistant/conversations/${a.conversation}`) },
441
474
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connection-centric",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The centric command: what a person does in Connection-Centric, done as them from a terminal, a script, or a program such as Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {