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 +1 -0
- package/centric.mjs +8 -6
- package/commands.mjs +52 -19
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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")
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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) =>
|
|
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}`)).
|
|
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: {
|
|
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
|
|
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