@wirecat/cli-core 0.424.0 → 0.426.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cli/author.d.ts +5 -6
  3. package/dist/cli/author.js +9 -16
  4. package/dist/cli/commands-command.js +1 -1
  5. package/dist/cli/config-issues.d.ts +20 -2
  6. package/dist/cli/config-issues.js +111 -17
  7. package/dist/cli/context.d.ts +2 -0
  8. package/dist/cli/context.js +17 -6
  9. package/dist/cli/dry-run-guard.d.ts +2 -0
  10. package/dist/cli/dry-run-guard.js +68 -0
  11. package/dist/cli/effects.d.ts +4 -0
  12. package/dist/cli/effects.js +5 -1
  13. package/dist/cli/entry.js +14 -9
  14. package/dist/cli/guide.d.ts +1 -1
  15. package/dist/cli/guide.js +33 -5
  16. package/dist/cli/help/command-help.js +12 -7
  17. package/dist/cli/help/first-screen.js +2 -2
  18. package/dist/cli/index.d.ts +5 -5
  19. package/dist/cli/index.js +5 -4
  20. package/dist/cli/list-query.d.ts +2 -0
  21. package/dist/cli/list-query.js +5 -2
  22. package/dist/cli/meeting-preparation-command.js +2 -2
  23. package/dist/cli/meeting-series-inventory-command.js +2 -2
  24. package/dist/cli/messenger/models-command.js +3 -2
  25. package/dist/cli/messenger/store-maintenance-command.js +15 -5
  26. package/dist/cli/preview.d.ts +44 -5
  27. package/dist/cli/preview.js +26 -8
  28. package/dist/cli/profile.d.ts +7 -0
  29. package/dist/cli/profile.js +19 -0
  30. package/dist/cli/program.js +42 -29
  31. package/dist/cli/runs/search.js +2 -0
  32. package/dist/cli/settings.js +13 -12
  33. package/dist/cli/shared-command-exports.js +3 -1
  34. package/dist/entities/index.d.ts +148 -0
  35. package/dist/entities/recorded-run.d.ts +84 -0
  36. package/dist/entities/recorded-run.js +15 -0
  37. package/dist/entities/store-maintenance.d.ts +64 -0
  38. package/dist/entities/store-maintenance.js +24 -1
  39. package/dist/mcp/agent.js +3 -4
  40. package/dist/mcp/bot/server.js +1 -1
  41. package/dist/mcp/meeting-preparation.js +2 -2
  42. package/dist/mcp/meeting-project-candidates.js +2 -2
  43. package/dist/mcp/meeting-series-inventory.js +2 -2
  44. package/dist/mcp/server.js +1 -1
  45. package/dist/mcp/surface.d.ts +3 -1
  46. package/dist/mcp/surface.js +20 -9
  47. package/dist/mcp/text.d.ts +1 -1
  48. package/dist/mcp/text.js +1 -1
  49. package/dist/mcp/tool.d.ts +1 -1
  50. package/dist/mcp/tool.js +3 -1
  51. package/dist/meetings/mcp/agent-text.d.ts +2 -0
  52. package/dist/meetings/mcp/agent-text.js +2 -0
  53. package/dist/meetings/mcp/index.d.ts +1 -1
  54. package/dist/meetings/mcp/index.js +3 -3
  55. package/dist/meetings/mcp/passage.js +2 -2
  56. package/dist/meetings/mcp/project-evidence.js +2 -2
  57. package/dist/meetings/mcp/search.js +2 -2
  58. package/dist/runs/error-detail.d.ts +42 -0
  59. package/dist/runs/error-detail.js +131 -0
  60. package/dist/runs/events.d.ts +4 -0
  61. package/dist/runs/events.js +2 -0
  62. package/dist/runs/log-attributes.js +2 -0
  63. package/dist/runs/recording.d.ts +5 -2
  64. package/dist/runs/recording.js +20 -19
  65. package/dist/runs/run-context.d.ts +37 -0
  66. package/dist/runs/run-context.js +148 -0
  67. package/dist/runs/run.d.ts +19 -3
  68. package/dist/runs/run.js +1 -0
  69. package/dist/store/exports.d.ts +2 -0
  70. package/dist/store/exports.js +2 -0
  71. package/dist/store/sqlite/store-contents.d.ts +47 -0
  72. package/dist/store/sqlite/store-contents.js +104 -0
  73. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { metaOf } from "@wirecat/cli-base/commands";
2
2
  import { writes } from "@wirecat/cli-base/standards";
3
3
  import { commandPathOf, outputOf } from "../command-contract.js";
4
4
  import { environmentOf } from "../context.js";
5
- import { PROFILE_FREE } from "../profile.js";
5
+ import { takesProfile } from "../profile.js";
6
6
  import { commandAt } from "../steps.js";
7
7
  import { short } from "./commands-text.js";
8
8
  /** Argument names onto the kinds `domain/references.ts` names; a plural takes the same kind. */
@@ -177,7 +177,7 @@ export const EXAMPLE_PROFILE = "personal";
177
177
  const withProfile = (command, example) => {
178
178
  const root = rootOf(command);
179
179
  const top = commandPathOf(command)[0];
180
- if (!profiled.has(root) || top === undefined || PROFILE_FREE.has(top))
180
+ if (!profiled.has(root) || top === undefined || !takesProfile(command))
181
181
  return example;
182
182
  const prefix = `${root.name()} ${top}`;
183
183
  return example === prefix || example.startsWith(`${prefix} `)
@@ -203,11 +203,16 @@ export const commandHelpText = (command) => {
203
203
  ...metaOf(command),
204
204
  path: commandPathOf(command),
205
205
  });
206
- const access = availability
207
- ? rows("Availability:", [
208
- availability.available ? "available for this profile" : `unavailable — ${availability.reason}`,
209
- ])
210
- : [];
206
+ // "Available for this profile" says nothing where no profile is in play: a tool without profiles, or a command
207
+ // that reads none.
208
+ const profileMatters = profiled.has(rootOf(command)) && takesProfile(command);
209
+ const access = !availability
210
+ ? []
211
+ : availability.available
212
+ ? profileMatters
213
+ ? rows("Availability:", ["available for this profile"])
214
+ : []
215
+ : rows("Availability:", [`unavailable — ${availability.reason}`]);
211
216
  return [
212
217
  "",
213
218
  ...access,
@@ -25,12 +25,12 @@ export const WIRECAT_TOOLS = [
25
25
  {
26
26
  command: "zm",
27
27
  package: "@wirecat/zoom-cli",
28
- does: "Zoom meetings: transcripts and summaries, searchable with your messages",
28
+ does: "Zoom meetings: transcripts and summaries, searchable with your other WireCat data",
29
29
  },
30
30
  {
31
31
  command: "drive",
32
32
  package: "@wirecat/drive-cli",
33
- does: "Google Drive and Docs as local text, searchable with your messages",
33
+ does: "Google Drive and Docs as local text, searchable with your other WireCat data",
34
34
  },
35
35
  {
36
36
  command: "memo",
@@ -11,7 +11,7 @@ export { callMeetingPreparationTool, meetingPreparationTool } from "../mcp/meeti
11
11
  export { callMeetingProjectCandidatesTool, meetingProjectCandidatesTool } from "../mcp/meeting-project-candidates.js";
12
12
  export { callMeetingSeriesInventoryTool, meetingSeriesInventoryTool } from "../mcp/meeting-series-inventory.js";
13
13
  export { registerLinkConversationsPrompt } from "../mcp/prompts.js";
14
- export { agentBody, agentJson, agentResult, UNTRUSTED, UNTRUSTED_INSTRUCTION } from "../mcp/text.js";
14
+ export { agentBody, agentJson, agentResult, UNTRUSTED, UNTRUSTED_INSTRUCTION, UNTRUSTED_NEUTRAL } from "../mcp/text.js";
15
15
  export { MEETING_RESULT_ENTITIES } from "../meetings/index.js";
16
16
  export { type AppIdentity, envName, readOnlyLockOf, readOnlyRefusal } from "../ports/app.js";
17
17
  export type { AccountEditing, AccountHealth, AccountRecords, AccountSettings, AccountStanding, AccountTools, After, BotChats, ChannelComments, ChatDeletion, ChatFolders, ChatReading, ContactBook, Download, ForumControl, ForumState, GroupAdmin, GroupModeration, HistoryBatch, HistoryChatEvent, HistoryReaction, HistoryStar, HistoryVote, InviteLinks, JoinRequests, LiveUpdates, MessageButtons, MessageEditing, MessageMedia, MessagePermalinks, MessagePins, MessagePolls, MessageReactions, MessageSearch, MessageStars, MessengerAdapter, MessengerCore, NewPoll, OfficialStats, PassiveUpdates, PersonProfiles, ProfileChange, ProfilePhotos, PushedHistory, ReadState, RemoteFile, ScheduledMessages, SenderIdentities, SenderSearch, SendOptions, Sent, ServerQuery, ServerReads, ThreadAddressing, TopicEditing, TopicHistory, TopicReadState, Transcript, } from "../ports/messenger.js";
@@ -52,7 +52,7 @@ export type { CommandAvailability, DiscoveredCommand } from "./command-availabil
52
52
  export { commandContract, type JsonSchema, SCHEMA_DIALECT } from "./command-contract.js";
53
53
  export { CONTRACT } from "./commands-command.js";
54
54
  export { refuseUnknownKey } from "./config-command.js";
55
- export type { ConfigIssue, ConfigIssueKind } from "./config-issues.js";
55
+ export { type ConfigIssue, type ConfigIssueKind, checkConfigText, issueMessage } from "./config-issues.js";
56
56
  export { loadSnakeCaseConfig } from "./config-keys.js";
57
57
  export { type AgentRun, type BaseContext, type BaseEnvironment, baseContext, environmentOf, outputFor, provide, secretInputOf, } from "./context.js";
58
58
  export { addContractInfo, type ContractEvidenceOptions, contractEvidence } from "./contract-evidence.js";
@@ -60,7 +60,7 @@ export { sharedCommandTemplate } from "./contract-templates.js";
60
60
  export { type Closeable, withDeadline } from "./deadline.js";
61
61
  export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
62
62
  export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
63
- export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
63
+ export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, previewFlag, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, storeChangeRefusal, } from "./effects.js";
64
64
  export { type CliEntry, type EntryContext, type EntryIo, startCli } from "./entry.js";
65
65
  export { type AnswerOutput, answerSchema, type Body, type ItemBody, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, type ListBody, type ListInput, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
66
66
  export { withAbort } from "./execution.js";
@@ -99,8 +99,8 @@ export { storeSummary } from "./messenger/store-maintenance-command.js";
99
99
  export { type Saving, stored } from "./messenger/stored.js";
100
100
  export { MODEL_CATALOGUE_QUERY, type ModelCatalogueOptions } from "./model-catalogue-command.js";
101
101
  export { window, withPaging } from "./paging.js";
102
- export { knownBeside, knownPermissionKeys, type PermissionKeyOf, unknownPermissionKeys } from "./permission-keys.js";
103
- export { asFirstWord, commandWords, liftProfile, refuseCommandName, rootOf, usableProfileName } from "./profile.js";
102
+ export { knownBeside, knownPermissionKeys, type PermissionKeyOf, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
103
+ export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
104
104
  export { createProgram, type ProgramDefinition, type ProgramOptions, type RunOptions, run } from "./program.js";
105
105
  export { fieldsOf, projectFields } from "./result-fields.js";
106
106
  export { type Config, type Configuration, count, first, flag, fromFile, type GlobalFlags, NONE_NAMED, namedProfile, type ProfileKind, plain, type ResolveOptions, type Settings, type SettingsExtension, type SettingsExtensionContext, type Source, settingsFor, } from "./settings.js";
package/dist/cli/index.js CHANGED
@@ -9,7 +9,7 @@ export { callMeetingPreparationTool, meetingPreparationTool } from "../mcp/meeti
9
9
  export { callMeetingProjectCandidatesTool, meetingProjectCandidatesTool } from "../mcp/meeting-project-candidates.js";
10
10
  export { callMeetingSeriesInventoryTool, meetingSeriesInventoryTool } from "../mcp/meeting-series-inventory.js";
11
11
  export { registerLinkConversationsPrompt } from "../mcp/prompts.js";
12
- export { agentBody, agentJson, agentResult, UNTRUSTED, UNTRUSTED_INSTRUCTION } from "../mcp/text.js";
12
+ export { agentBody, agentJson, agentResult, UNTRUSTED, UNTRUSTED_INSTRUCTION, UNTRUSTED_NEUTRAL } from "../mcp/text.js";
13
13
  export { MEETING_RESULT_ENTITIES } from "../meetings/index.js";
14
14
  export { envName, readOnlyLockOf, readOnlyRefusal } from "../ports/app.js";
15
15
  export { senderFacts } from "../replies/dry-run.js";
@@ -45,6 +45,7 @@ export { PressLog, UpdatesCursor } from "./bot/updates.js";
45
45
  export { commandContract, SCHEMA_DIALECT } from "./command-contract.js";
46
46
  export { CONTRACT } from "./commands-command.js";
47
47
  export { refuseUnknownKey } from "./config-command.js";
48
+ export { checkConfigText, issueMessage } from "./config-issues.js";
48
49
  export { loadSnakeCaseConfig } from "./config-keys.js";
49
50
  export { baseContext, environmentOf, outputFor, provide, secretInputOf, } from "./context.js";
50
51
  export { addContractInfo, contractEvidence } from "./contract-evidence.js";
@@ -52,7 +53,7 @@ export { sharedCommandTemplate } from "./contract-templates.js";
52
53
  export { withDeadline } from "./deadline.js";
53
54
  export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
54
55
  export { DISCOVERY_DESCRIPTIONS } from "./discovery-descriptions.js";
55
- export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
56
+ export { changesAuthority, changesAuthorityAt, changesAuthorityNow, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, previewFlag, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, storeChangeRefusal, } from "./effects.js";
56
57
  export { startCli } from "./entry.js";
57
58
  export { answerSchema, isBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
58
59
  export { withAbort } from "./execution.js";
@@ -89,8 +90,8 @@ export { storeSummary } from "./messenger/store-maintenance-command.js";
89
90
  export { stored } from "./messenger/stored.js";
90
91
  export { MODEL_CATALOGUE_QUERY } from "./model-catalogue-command.js";
91
92
  export { window, withPaging } from "./paging.js";
92
- export { knownBeside, knownPermissionKeys, unknownPermissionKeys } from "./permission-keys.js";
93
- export { asFirstWord, commandWords, liftProfile, refuseCommandName, rootOf, usableProfileName } from "./profile.js";
93
+ export { knownBeside, knownPermissionKeys, unknownPermissionIssues, unknownPermissionKeys, } from "./permission-keys.js";
94
+ export { asFirstWord, commandWords, liftProfile, profileFree, refuseCommandName, rootOf, takesProfile, usableProfileName, } from "./profile.js";
94
95
  export { createProgram, run } from "./program.js";
95
96
  export { fieldsOf, projectFields } from "./result-fields.js";
96
97
  export { count, first, flag, fromFile, NONE_NAMED, namedProfile, plain, settingsFor, } from "./settings.js";
@@ -47,6 +47,8 @@ export declare const queryTerms: (words: QueryWords, registry: QueryRegistry, co
47
47
  /**
48
48
  * A `time` term as epoch ms bounds: `time>=7d`, `time:[2026-10-01 TO 2026-10-07]`, `time:today`, and the `meta`
49
49
  * an answer reports them by: the zone calendar words and bare dates were read in, and the bounds in UTC.
50
+ * `until` is inclusive: an exclusive end (a bare date's next midnight, `… TO …}`) comes back 1 ms inside. `since` stays
51
+ * as written, because the inbox reads strictly after a `date>` start.
50
52
  */
51
53
  export declare const timeBounds: (term: Predicate | undefined, zone?: string) => {
52
54
  since?: number;
@@ -133,20 +133,23 @@ export const queryTerms = (words, registry, command) => {
133
133
  /**
134
134
  * A `time` term as epoch ms bounds: `time>=7d`, `time:[2026-10-01 TO 2026-10-07]`, `time:today`, and the `meta`
135
135
  * an answer reports them by: the zone calendar words and bare dates were read in, and the bounds in UTC.
136
+ * `until` is inclusive: an exclusive end (a bare date's next midnight, `… TO …}`) comes back 1 ms inside. `since` stays
137
+ * as written, because the inbox reads strictly after a `date>` start.
136
138
  */
137
139
  export const timeBounds = (term, zone = timezoneOf()) => {
138
140
  if (!term)
139
141
  return {};
140
142
  const upper = term.operator === "range" ? (term.upper ?? "*") : term.value;
141
143
  const range = dateRange(term.value, upper, term.operator === "range" ? term.lower_inclusive !== false : true, term.operator === "range" ? term.upper_inclusive !== false : true, zone, term.span);
144
+ const until = range.upper === undefined ? undefined : range.upper - (range.upper_inclusive ? 0 : 1);
142
145
  return {
143
146
  ...(range.lower === undefined ? {} : { since: range.lower }),
144
- ...(range.upper === undefined ? {} : { until: range.upper }),
147
+ ...(until === undefined ? {} : { until }),
145
148
  meta: {
146
149
  timezone: zone,
147
150
  window: {
148
151
  ...(range.lower === undefined ? {} : { since: new Date(range.lower).toISOString() }),
149
- ...(range.upper === undefined ? {} : { until: new Date(range.upper).toISOString() }),
152
+ ...(until === undefined ? {} : { until: new Date(until).toISOString() }),
150
153
  },
151
154
  },
152
155
  };
@@ -1,6 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
- import { REMOTE_READ } from "./effects.js";
3
+ import { previewFlag, REMOTE_READ } from "./effects.js";
4
4
  import { communicationOptions } from "./meeting-communications.js";
5
5
  import { readMeetingPreparation } from "./meeting-preparation-read.js";
6
6
  const integer = (value) => {
@@ -48,6 +48,6 @@ export const addMeetingPreparationCommand = (program, deps) => {
48
48
  origin: "handwritten",
49
49
  effects: REMOTE_READ,
50
50
  output: { entity: "meeting_preparation" },
51
- examples: [`${program.name()} meetings prepare 123 --json`],
51
+ examples: [`${program.name()} meetings prepare 123${previewFlag(program)} --json`],
52
52
  });
53
53
  };
@@ -1,6 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { annotate } from "@wirecat/cli-base/commands";
3
- import { REMOTE_READ } from "./effects.js";
3
+ import { previewFlag, REMOTE_READ } from "./effects.js";
4
4
  import { readMeetingSeriesInventory } from "./meeting-series-inventory-read.js";
5
5
  const integer = (value) => {
6
6
  if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)))
@@ -32,6 +32,6 @@ export const addMeetingSeriesInventoryCommand = (program, deps) => {
32
32
  effects: REMOTE_READ,
33
33
  offline: false,
34
34
  output: { entity: "meeting_series_inventory" },
35
- examples: [`${program.name()} meetings scheduled --json`],
35
+ examples: [`${program.name()} meetings scheduled${previewFlag(program)} --json`],
36
36
  });
37
37
  };
@@ -165,9 +165,10 @@ export const modelsCommand = (messenger) => {
165
165
  reachesOtherService(annotate(models.command("download"), {
166
166
  effects: FETCH,
167
167
  output: { entity: "model" },
168
- examples: [`${tool} models download parakeet-v3`, `${tool} models download ${DEFAULT_TEXT_MODEL}`],
168
+ examples: [`${tool} models download ${DEFAULT_TEXT_MODEL}`],
169
169
  }))
170
- .description("download and verify one speech or embedding model")
170
+ .summary("download and verify one speech or embedding model")
171
+ .description("download and verify one speech or embedding model: tens to hundreds of MB, each size in models list")
171
172
  .argument("<model>", "a model id from models list")
172
173
  .option("--accept-terms", "accept the model's licence terms, for a model that has its own")
173
174
  .action(async function (id, options) {
@@ -11,7 +11,7 @@ import { MIGRATIONS, migrate } from "../../store/migrations.js";
11
11
  import { openCache } from "../../store/open.js";
12
12
  import { storePath } from "../../store/path.js";
13
13
  import { deleteCopy, repairStore } from "../../store/repair.js";
14
- import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, searchIndexState, stemmerCache, stemsState, } from "../../store/store.js";
14
+ import { drainNoteIndex, fillSearchIndex, fillStems, involvementStoreOver, noteIndexState, openSqlite, openStore, orphanPointers, pendingNormalization, resetAttachmentWords, resetNoteIndex, resetSearchIndex, resetStems, rowCounts, searchIndexState, stemmerCache, stemsState, storeContents, } from "../../store/store.js";
15
15
  import { environmentOf, outputFor } from "../context.js";
16
16
  import { destroysNothing, LOCAL_READ, LOCAL_WRITE } from "../effects.js";
17
17
  import { itemOf, renderAnswer, snakeKeys } from "../envelope.js";
@@ -49,6 +49,16 @@ const reading = async (path, read) => {
49
49
  database.close();
50
50
  }
51
51
  };
52
+ /** For a command that only reads: the connection refuses writes, and opening sets no journal mode. */
53
+ const readingOnly = async (path, read) => {
54
+ const { database } = await openSqlite(path, { readOnly: true });
55
+ try {
56
+ return read(database);
57
+ }
58
+ finally {
59
+ database.close();
60
+ }
61
+ };
52
62
  /** `doctor`'s short summary of the store. Must answer for a broken file, so an error is a field. */
53
63
  export const storeSummary = async (env) => {
54
64
  const path = storePath(env);
@@ -154,25 +164,25 @@ const copiesCommand = () => new Command("copies").description("the tables `store
154
164
  }));
155
165
  const infoCommand = () => annotate(new Command("info"), { output: { entity: "store_info" }, effects: LOCAL_READ })
156
166
  .summary("the local archive's format, size and contents")
157
- .description("the store file: where it is, its size, its schema and how many rows it holds; changes nothing")
167
+ .description("the store file: where it is, its size, its schema and how many rows it holds, by provider, chat kind, account, year (UTC) and attachment kind, over every account; changes nothing")
158
168
  .action(async function () {
159
169
  const path = storePath(environmentOf(this).env ?? process.env);
160
170
  if (!existsSync(path)) {
161
171
  result(this, { path, exists: false });
162
172
  return;
163
173
  }
164
- const read = reading(path, (database) => {
174
+ const read = readingOnly(path, (database) => {
165
175
  const { version, minCompatible, writable } = schemaOf(database);
166
- const tables = ["accounts", "chats", "messages", "attachments", "identities"];
167
176
  return {
168
177
  path,
169
178
  exists: true,
170
179
  bytes: { file: bytesOf(path), wal: bytesOf(`${path}-wal`) },
171
180
  schema: { version, minCompatible, speaks: SPEAKS, writable },
172
- rows: version > 0 ? Object.fromEntries(tables.map((table) => [table, count(database, table)])) : {},
181
+ rows: version > 0 ? rowCounts(database) : {},
173
182
  pendingNormalization: pendingIfKnown(database),
174
183
  wordIndex: searchIndexState(database) ?? null,
175
184
  stemIndex: stemsState(database) ?? null,
185
+ ...(version > 0 ? storeContents(database) : {}),
176
186
  };
177
187
  });
178
188
  result(this, await read.catch((error) => ({ path, exists: true, opens: false, error: messageOf(error) })));
@@ -3,13 +3,51 @@ import { type PermissionKey } from "../sends/permissions.js";
3
3
  import type { Settings } from "./settings.js";
4
4
  export declare class PreviewComplete extends Error {
5
5
  }
6
- export declare const preview: (command: Command, settings: Pick<Settings, "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "profile">, tool: string, keyOf?: (path: readonly string[]) => PermissionKey | null | undefined) => {
6
+ export declare const preview: (command: Command, settings: Pick<Settings, "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "profile">, tool: string, keyOf?: (path: readonly string[]) => PermissionKey | null | undefined, messenger?: boolean) => {
7
7
  preview: boolean;
8
8
  path: string[];
9
- permission: {
10
- key: string | null;
11
- level: "allow" | "ask" | "readonly";
9
+ permission?: {
10
+ key: PermissionKey | null;
11
+ level: string;
12
+ } | undefined;
13
+ validation: {
14
+ arguments: string;
15
+ permissions: string;
16
+ targets: string;
17
+ businessRules: string;
18
+ };
19
+ effects: {
20
+ actionInvoked: boolean;
21
+ writeReserved: boolean;
22
+ messengerContacted?: boolean | undefined;
23
+ serviceContacted: boolean;
24
+ configurationRead: boolean;
12
25
  };
26
+ arguments: ({
27
+ name: string;
28
+ reference: string;
29
+ } | {
30
+ name: string;
31
+ supplied: boolean;
32
+ bytes: number | undefined;
33
+ })[];
34
+ options: string[];
35
+ trust: string;
36
+ };
37
+ /**
38
+ * `messenger_contacted` stays for the messengers, whose callers may read it; `service_contacted` says the same
39
+ * for every tool. Without `permission` nothing was checked: a host that is not `run` has no settings to check.
40
+ */
41
+ export declare const previewBody: (command: Command, permission: {
42
+ key: PermissionKey | null;
43
+ level: string;
44
+ } | undefined, messenger: boolean) => {
45
+ preview: boolean;
46
+ path: string[];
47
+ permission?: {
48
+ key: PermissionKey | null;
49
+ level: string;
50
+ } | undefined;
13
51
  validation: {
14
52
  arguments: string;
15
53
  permissions: string;
@@ -19,7 +57,8 @@ export declare const preview: (command: Command, settings: Pick<Settings, "permi
19
57
  effects: {
20
58
  actionInvoked: boolean;
21
59
  writeReserved: boolean;
22
- messengerContacted: boolean;
60
+ messengerContacted?: boolean | undefined;
61
+ serviceContacted: boolean;
23
62
  configurationRead: boolean;
24
63
  };
25
64
  arguments: ({
@@ -6,11 +6,11 @@ import { commandPathOf } from "./command-contract.js";
6
6
  import { refuseUnknownPermissionKeys } from "./permission-keys.js";
7
7
  export class PreviewComplete extends Error {
8
8
  }
9
- export const preview = (command, settings, tool, keyOf) => {
9
+ export const preview = (command, settings, tool, keyOf, messenger = true) => {
10
10
  const path = commandPathOf(command);
11
11
  const { permissions } = settings;
12
12
  refuseUnknownPermissionKeys(command, settings, tool, keyOf && ((one) => keyOf(commandPathOf(one))));
13
- const key = keyForCommand(path);
13
+ const key = keyOf?.(path) ?? keyForCommand(path);
14
14
  const level = key ? levelFor(permissions, key).level : "allow";
15
15
  for (const source of readKeysForCommand(path)) {
16
16
  if (levelFor(permissions, source).level === "deny")
@@ -21,18 +21,36 @@ export const preview = (command, settings, tool, keyOf) => {
21
21
  (key !== null && key !== undefined && WRITE_KEYS.includes(key));
22
22
  if (level === "deny" || (writes && level === "readonly"))
23
23
  throw new CliError("permission_error", `this profile does not permit ${path.join(" ")}`, { permission: key });
24
- const targets = new Set(["chat", "message", "person", "profile"]);
24
+ return previewBody(command, { key: key ?? null, level }, messenger);
25
+ };
26
+ const TARGETS = new Set(["chat", "message", "person", "profile"]);
27
+ /**
28
+ * `messenger_contacted` stays for the messengers, whose callers may read it; `service_contacted` says the same
29
+ * for every tool. Without `permission` nothing was checked: a host that is not `run` has no settings to check.
30
+ */
31
+ export const previewBody = (command, permission, messenger) => {
25
32
  return {
26
33
  preview: true,
27
- path,
28
- permission: { key: key ?? null, level },
29
- validation: { arguments: "parsed", permissions: "checked", targets: "unresolved", businessRules: "not-executed" },
30
- effects: { actionInvoked: false, writeReserved: false, messengerContacted: false, configurationRead: true },
34
+ path: commandPathOf(command),
35
+ ...(permission ? { permission } : {}),
36
+ validation: {
37
+ arguments: "parsed",
38
+ permissions: permission ? "checked" : "not-checked",
39
+ targets: "unresolved",
40
+ businessRules: "not-executed",
41
+ },
42
+ effects: {
43
+ actionInvoked: false,
44
+ writeReserved: false,
45
+ ...(messenger ? { messengerContacted: false } : {}),
46
+ serviceContacted: false,
47
+ configurationRead: permission !== undefined,
48
+ },
31
49
  arguments: command.registeredArguments.map((argument, at) => {
32
50
  const value = command.processedArgs[at];
33
51
  return {
34
52
  name: argument.name(),
35
- ...(targets.has(argument.name()) && typeof value === "string"
53
+ ...(TARGETS.has(argument.name()) && typeof value === "string"
36
54
  ? { reference: value }
37
55
  : { supplied: value !== undefined, bytes: typeof value === "string" ? Buffer.byteLength(value) : undefined }),
38
56
  };
@@ -4,6 +4,13 @@ export declare const asFirstWord: (profile: string) => string;
4
4
  export declare const usableProfileName: (profile: string) => string;
5
5
  /** The top-level commands that never read a profile; every other one is `<tool> <profile> …`. */
6
6
  export declare const PROFILE_FREE: ReadonlySet<string>;
7
+ /**
8
+ * Marks a command, and every command under it, as one that reads no profile, so help shows it without one:
9
+ * `drive doctor`, not `drive personal doctor`. Which commands those are is the CLI's to say.
10
+ */
11
+ export declare const profileFree: <T extends Command>(command: T) => T;
12
+ /** Whether the command line puts a profile before this command: not for `PROFILE_FREE` or a `profileFree` one. */
13
+ export declare const takesProfile: (command: Command) => boolean;
7
14
  /**
8
15
  * Every word that means a command at the top level. Taken off the built program rather than
9
16
  * written out, because a list written out goes stale the day a command is added — and `help` is in
@@ -16,6 +16,25 @@ export const usableProfileName = (profile) => {
16
16
  };
17
17
  /** The top-level commands that never read a profile; every other one is `<tool> <profile> …`. */
18
18
  export const PROFILE_FREE = new Set(["commands", "complete", "guide", "help", "skill", "upgrade"]);
19
+ const freeOfProfile = new WeakSet();
20
+ /**
21
+ * Marks a command, and every command under it, as one that reads no profile, so help shows it without one:
22
+ * `drive doctor`, not `drive personal doctor`. Which commands those are is the CLI's to say.
23
+ */
24
+ export const profileFree = (command) => {
25
+ freeOfProfile.add(command);
26
+ return command;
27
+ };
28
+ /** Whether the command line puts a profile before this command: not for `PROFILE_FREE` or a `profileFree` one. */
29
+ export const takesProfile = (command) => {
30
+ let top;
31
+ for (let at = command; at?.parent; at = at.parent) {
32
+ if (freeOfProfile.has(at))
33
+ return false;
34
+ top = at;
35
+ }
36
+ return top !== undefined && !PROFILE_FREE.has(top.name());
37
+ };
19
38
  /**
20
39
  * Every word that means a command at the top level. Taken off the built program rather than
21
40
  * written out, because a list written out goes stale the day a command is added — and `help` is in
@@ -10,6 +10,7 @@ import { isCliFailure, isCommanderFailure } from "../errors/failures.js";
10
10
  import { failureContext } from "../errors/reasons.js";
11
11
  import { envName, readOnlyLockOf, readOnlyRefusal } from "../ports/app.js";
12
12
  import { recorded, wasSettled } from "../runs/recording.js";
13
+ import { runContextOf } from "../runs/run-context.js";
13
14
  import { processRun, useAsProcessRun } from "../runs/scope.js";
14
15
  import { AUTHORITY_KEY, BOT_API_KEY, isBotApi, keyForCommand } from "../sends/permissions.js";
15
16
  import { logParsedArgv, logVersion } from "./argv-log.js";
@@ -17,7 +18,7 @@ import { commandAvailability } from "./command-availability.js";
17
18
  import { commandPathOf } from "./command-contract.js";
18
19
  import { outputFor, provide } from "./context.js";
19
20
  import { refuseWriteBeforeConnecting } from "./early-refusal.js";
20
- import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, STORE_CHANGE_REFUSAL, } from "./effects.js";
21
+ import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
21
22
  import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
22
23
  import { byteCount, DEFAULT_COMMAND_MS, DEFAULT_OUTPUT_BYTES, execution } from "./execution.js";
23
24
  import { setupAtStart } from "./first-run.js";
@@ -26,7 +27,7 @@ import { showInputFields } from "./input-fields.js";
26
27
  import { MAX_BUFFERED_INPUT, provideInputPolicy } from "./input-policy.js";
27
28
  import { checkJson, liftJson } from "./json-fields.js";
28
29
  import { PreviewComplete, preview } from "./preview.js";
29
- import { commandWords, liftProfile, PROFILE_FREE, sampleCommand, valuedOptions } from "./profile.js";
30
+ import { commandWords, liftProfile, sampleCommand, takesProfile, valuedOptions } from "./profile.js";
30
31
  import { absolutePaths, relayTarget } from "./relay-route.js";
31
32
  import { NoProfileHere, namedProfile, settingsFor, } from "./settings.js";
32
33
  import { assertSharedContracts } from "./shared-contracts.js";
@@ -73,14 +74,15 @@ export const createProgram = ({ app, commands, configure, firstScreen }, { out,
73
74
  assertSharedContracts(program);
74
75
  installHelp(program, { pitch: app.description, ...firstScreen });
75
76
  // Commander builds a usage line from the command tree, where the profile is not a word.
76
- for (const top of program.commands) {
77
- if (PROFILE_FREE.has(top.name()))
78
- continue;
79
- forEachCommand(top, (command) => command.configureHelp({
80
- ...command.configureHelp(),
81
- commandUsage: (shown) => `${app.command} <profile>${new Help().commandUsage(shown).slice(app.command.length)}`,
82
- }));
83
- }
77
+ if (firstScreen?.profile !== false)
78
+ for (const top of program.commands)
79
+ forEachCommand(top, (command) => command.configureHelp({
80
+ ...command.configureHelp(),
81
+ commandUsage: (shown) => {
82
+ const usage = new Help().commandUsage(shown);
83
+ return takesProfile(shown) ? `${app.command} <profile>${usage.slice(app.command.length)}` : usage;
84
+ },
85
+ }));
84
86
  // Depth-first: Commander does not pass `configureOutput` down to a command added with
85
87
  // `addCommand`, so `tg messages --help` would write to the real terminal.
86
88
  if (out || err) {
@@ -191,6 +193,7 @@ const runOnce = async (argv, definition, options) => {
191
193
  const exit = {};
192
194
  const environment = {
193
195
  ...options,
196
+ argv,
194
197
  streams: control.streams,
195
198
  signal: options.signal ? AbortSignal.any([control.signal, options.signal]) : control.signal,
196
199
  commandSignal: control.signal,
@@ -267,20 +270,22 @@ const runOnce = async (argv, definition, options) => {
267
270
  const path = commandPathOf(action);
268
271
  const lock = readOnlyLockOf(definition.app, commandEnv);
269
272
  const input = options.stdin ?? process.stdin;
270
- await setupAtStart({
271
- command,
272
- env: commandEnv,
273
- contentLanguages: definition.app.contentLanguages,
274
- path,
275
- headless,
276
- readOnlyLock: lock,
277
- input,
278
- terminal: !options.streams && input.isTTY === true && process.stderr.isTTY === true,
279
- });
273
+ // The first-run questions write the shared config; a dry run writes nothing.
274
+ if (!flags.dryRun)
275
+ await setupAtStart({
276
+ command,
277
+ env: commandEnv,
278
+ contentLanguages: definition.app.contentLanguages,
279
+ path,
280
+ headless,
281
+ readOnlyLock: lock,
282
+ input,
283
+ terminal: !options.streams && input.isTTY === true && process.stderr.isTTY === true,
284
+ });
280
285
  if (lock && isLogin(action) && !flags.dryRun)
281
286
  throw readOnlyRefusal(lock, "log in or out");
282
287
  if (lock && isStoreChange(action) && !flags.dryRun)
283
- throw readOnlyRefusal(lock, STORE_CHANGE_REFUSAL);
288
+ throw readOnlyRefusal(lock, storeChangeRefusal(definition.app));
284
289
  refuseWriteBeforeConnecting(action, {
285
290
  app: definition.app,
286
291
  env: commandEnv,
@@ -385,7 +390,7 @@ const runOnce = async (argv, definition, options) => {
385
390
  configPath: resolved.configPath ?? "",
386
391
  profile: resolved.profile,
387
392
  ...(resolved.savedPermissions ? { savedPermissions: resolved.savedPermissions } : {}),
388
- }, command, definition.configuration?.permissionKey);
393
+ }, command, definition.configuration?.permissionKey, definition.app.messenger !== false);
389
394
  renderAnswer(action, outputFor(action), itemOf(snakeKeys(previewed)));
390
395
  throw new PreviewComplete();
391
396
  }
@@ -412,7 +417,7 @@ const runOnce = async (argv, definition, options) => {
412
417
  refuseJoinedPath(program, words.filter((word) => !word.startsWith("-")), profile !== undefined && !/\s/.test(profile) ? [profile] : []);
413
418
  }
414
419
  catch (failure) {
415
- const settled = await settleFailure(failure, { definition, program, rest: lifted, profile, options });
420
+ const settled = await settleFailure(failure, { definition, program, rest: lifted, argv, profile, options });
416
421
  return report(streams, reporting, failed(definition, program, lifted, failure, settled, debug));
417
422
  }
418
423
  if (profile !== undefined)
@@ -427,7 +432,7 @@ const runOnce = async (argv, definition, options) => {
427
432
  const message = `"${profile}" is not a command, so it was read as a profile name — and no command followed it. ` +
428
433
  `Run \`${command} --help\` for the commands, or \`${command} ${profile} account show\` if "${profile}" is your profile.`;
429
434
  const failure = new CliError("validation_error", message, { param: "profile" });
430
- const settled = await settleFailure(failure, { definition, program, rest, profile, options });
435
+ const settled = await settleFailure(failure, { definition, program, rest, argv, profile, options });
431
436
  return report(streams, reporting, failed(definition, program, rest, failure, settled, debug));
432
437
  }
433
438
  try {
@@ -472,10 +477,10 @@ const runOnce = async (argv, definition, options) => {
472
477
  message += ` — "${profile}" is not a command, so it was read as a profile name — which left "${rest[0]}" to be one.`;
473
478
  }
474
479
  const failure = new CliError("validation_error", message);
475
- const settled = await settleFailure(failure, { definition, program, rest, profile, options });
480
+ const settled = await settleFailure(failure, { definition, program, rest, argv, profile, options });
476
481
  return report(streams, reporting, failed(definition, program, rest, failure, settled, debug));
477
482
  }
478
- const settled = await settleFailure(error, { definition, program, rest, profile, options });
483
+ const settled = await settleFailure(error, { definition, program, rest, argv, profile, options });
479
484
  const exit = report(streams, reporting, failed(definition, program, rest, error, settled, debug));
480
485
  return signalExit ?? exit;
481
486
  }
@@ -562,9 +567,9 @@ const settleFailure = async (failure, state) => {
562
567
  /**
563
568
  * **Every failure is kept as a run** — a usage error, a configuration that will not load, a command
564
569
  * that never opens a run — unless recording was turned off. One its own run already kept is skipped.
565
- * Only the command's words are named, never its arguments: those can be a message.
570
+ * Its arguments are kept masked (`maskArgv`): a value that can be a message never is.
566
571
  */
567
- const keepFailure = async (failure, { definition, program, rest, profile, options }) => {
572
+ const keepFailure = async (failure, { definition, program, rest, argv, profile, options }) => {
568
573
  if (wasSettled(failure))
569
574
  return;
570
575
  const env = options.env ?? process.env;
@@ -577,9 +582,17 @@ const keepFailure = async (failure, { definition, program, rest, profile, option
577
582
  catch {
578
583
  // A configuration that will not load is a failure worth keeping too; the flags are all there is to go on.
579
584
  }
585
+ const command = `${options.agent ? "mcp " : ""}${path || definition.app.command}`;
580
586
  await recorded({
581
587
  app: definition.app,
582
- command: `${options.agent ? "mcp " : ""}${path || definition.app.command}`,
588
+ command,
589
+ context: runContextOf({
590
+ bin: definition.app.command,
591
+ command: path ? command : options.agent ? "mcp" : "",
592
+ program,
593
+ argv,
594
+ env,
595
+ }),
583
596
  profile: settings ? namedProfile(settings) : profile,
584
597
  record: false,
585
598
  keepFailed: settings?.keepFailedRuns ?? !rest.includes("--no-record"),
@@ -8,6 +8,8 @@ const FIELDS = [
8
8
  "opcode",
9
9
  "seq",
10
10
  "status",
11
+ "method",
12
+ "endpoint_template",
11
13
  "bytes",
12
14
  "ids",
13
15
  "counts",