ai-hist 0.16.0 → 0.18.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/README.md +62 -84
- package/dist/cli.js +80 -103
- package/dist/cli.js.map +1 -1
- package/dist/contracts.d.ts +413 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +2 -0
- package/dist/contracts.js.map +1 -0
- package/dist/delivery-cli.d.ts +22 -0
- package/dist/delivery-cli.d.ts.map +1 -0
- package/dist/delivery-cli.js +135 -0
- package/dist/delivery-cli.js.map +1 -0
- package/dist/delivery-contracts.d.ts +149 -0
- package/dist/delivery-contracts.d.ts.map +1 -0
- package/dist/delivery-contracts.js +4 -0
- package/dist/delivery-contracts.js.map +1 -0
- package/dist/delivery-plugins.d.ts +37 -0
- package/dist/delivery-plugins.d.ts.map +1 -0
- package/dist/delivery-plugins.js +131 -0
- package/dist/delivery-plugins.js.map +1 -0
- package/dist/delivery.d.ts +62 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +156 -0
- package/dist/delivery.js.map +1 -0
- package/dist/delivery.test.d.ts +2 -0
- package/dist/delivery.test.d.ts.map +1 -0
- package/dist/delivery.test.js +396 -0
- package/dist/delivery.test.js.map +1 -0
- package/dist/git.d.ts +17 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +13 -0
- package/dist/git.js.map +1 -0
- package/dist/history-export.d.ts +30 -0
- package/dist/history-export.d.ts.map +1 -0
- package/dist/history-export.js +42 -0
- package/dist/history-export.js.map +1 -0
- package/dist/index.d.ts +5 -533
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -833
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +31 -30
- package/dist/mcp-server.js.map +1 -1
- package/dist/native.d.ts +6 -15
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +1 -1
- package/dist/native.js.map +1 -1
- package/dist/normalization.d.ts +65 -0
- package/dist/normalization.d.ts.map +1 -0
- package/dist/normalization.js +354 -0
- package/dist/normalization.js.map +1 -0
- package/dist/operations.d.ts +101 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +677 -0
- package/dist/operations.js.map +1 -0
- package/dist/pagination.d.ts +25 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +162 -0
- package/dist/pagination.js.map +1 -0
- package/dist/source-contracts.d.ts +63 -0
- package/dist/source-contracts.d.ts.map +1 -0
- package/dist/source-contracts.js +2 -0
- package/dist/source-contracts.js.map +1 -0
- package/dist/source-plugins.d.ts +35 -0
- package/dist/source-plugins.d.ts.map +1 -0
- package/dist/source-plugins.js +177 -0
- package/dist/source-plugins.js.map +1 -0
- package/dist/source-plugins.test.d.ts +2 -0
- package/dist/source-plugins.test.d.ts.map +1 -0
- package/dist/source-plugins.test.js +379 -0
- package/dist/source-plugins.test.js.map +1 -0
- package/package.json +18 -18
- package/dist/cloud-auth-bundle.d.ts +0 -2
- package/dist/cloud-auth-bundle.d.ts.map +0 -1
- package/dist/cloud-auth-bundle.js +0 -1112
- package/dist/cloud-auth-bundle.js.map +0 -7
- package/dist/cloud-client.d.ts +0 -221
- package/dist/cloud-client.d.ts.map +0 -1
- package/dist/cloud-client.js +0 -283
- package/dist/cloud-client.js.map +0 -1
- package/dist/cloud-preflight.d.ts +0 -29
- package/dist/cloud-preflight.d.ts.map +0 -1
- package/dist/cloud-preflight.js +0 -72
- package/dist/cloud-preflight.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# ai-hist
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
The local history TypeScript SDK, Node CLI, and MCP server. Storage and local
|
|
4
|
+
ingestion use one mandatory `ai-hist-native` Node-API engine. Optional source and
|
|
5
|
+
destination plugins compose through public interfaces; no JavaScript SQL or
|
|
6
|
+
commercial dependency is required by this package.
|
|
6
7
|
|
|
7
8
|
```bash
|
|
8
9
|
npm install ai-hist
|
|
@@ -50,7 +51,8 @@ cache-only. `discoverSessions` is shallow discovery. `hydrateSession` is
|
|
|
50
51
|
targeted evidence acquisition for one existing catalog row. It returns
|
|
51
52
|
`hydrated`, `updated`, `unchanged`, or `capability_limited`, an indexed source
|
|
52
53
|
stamp, evidence counts, related native session IDs, and bounded-work metrics.
|
|
53
|
-
Targeted remote hydration uses `scope: 'remote'
|
|
54
|
+
Targeted remote hydration uses `scope: 'remote'` and an explicitly configured
|
|
55
|
+
source plugin registry: Claude web sessions can
|
|
54
56
|
return `capability: 'full'` after complete teleport evidence is acquired;
|
|
55
57
|
Codex cloud tasks return `partial` when their supported unified diff is indexed
|
|
56
58
|
or `shallow_only` when no richer evidence is exposed. Partial results retain
|
|
@@ -70,14 +72,29 @@ offline behavior and making provider-cloud access explicit. The CLI exposes the
|
|
|
70
72
|
same mutually exclusive `--local`, `--remote`, and `--all` flags; omitting them
|
|
71
73
|
is equivalent to `--local`.
|
|
72
74
|
|
|
73
|
-
Cached reads
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
Cached reads preserve the requested scope and never consult commercial auth.
|
|
76
|
+
Stored remote history can be queried with absent, malformed, expired, or
|
|
77
|
+
ambiguous credentials. Remote acquisition requires an explicitly loaded plugin
|
|
78
|
+
registry. Install `@agent-relay/history-provider-sources` for Claude web/Codex
|
|
79
|
+
cloud, or `@agent-relay/relayhistory` for RelayHistory. Installing a package does
|
|
80
|
+
not register it, inspect auth or start delivery.
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { HistoryPluginRegistry } from 'ai-hist';
|
|
84
|
+
import { createHistoryPlugin } from '@agent-relay/history-provider-sources';
|
|
85
|
+
const plugins = new HistoryPluginRegistry();
|
|
86
|
+
plugins.register(createHistoryPlugin({connectors:['claude-web']}));
|
|
87
|
+
await discoverSessions({ scope:'remote', plugins, sourceConnectors:['claude-web'] });
|
|
88
|
+
await sync({ scope:'all', plugins, sourceConnectors:[] }); // local adapters only
|
|
89
|
+
const cached = await stats({ scope:'remote' }); // no login required
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The CLI accepts `--config history.json` on acquisition commands. Config lists
|
|
93
|
+
explicit plugin modules/options; `--source-connector` selects their IDs and is
|
|
94
|
+
repeatable. `--no-source-connectors` disables remote acquisition. MCP uses
|
|
95
|
+
`AI_HIST_PLUGIN_CONFIG` and `source_connectors`. Scope defaults to local even
|
|
96
|
+
with plugins configured. See [source plugins](../docs/remote-connectors.md).
|
|
97
|
+
Native contract 14 rejects old addons that lack the source/delivery boundary.
|
|
81
98
|
|
|
82
99
|
Catalog pages, discovery results, statistics, and sync results echo the requested `scope`,
|
|
83
100
|
and discovery results additionally report `locationsRun` — the connector
|
|
@@ -188,75 +205,36 @@ evidence, and connector/parser failures with dedicated error subclasses.
|
|
|
188
205
|
The old synchronous `AiHist` class and `openAiHist()` API were removed in 1.0.
|
|
189
206
|
See [the migration guide](https://github.com/AgentWorkforce/relayhistory/blob/main/docs/native-sdk-migration.md).
|
|
190
207
|
|
|
191
|
-
##
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
session: null, outcomes: [], links: [], nextCursor: null,
|
|
225
|
-
})),
|
|
226
|
-
},
|
|
227
|
-
);
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
This resolver disables automatic refresh. A 401 from the selected transport
|
|
231
|
-
throws `AuthenticationExpiredError` without an authentication request. The
|
|
232
|
-
default `resolveCloudSession` returns the marker for stored credentials and
|
|
233
|
-
enables native refresh.
|
|
234
|
-
|
|
235
|
-
## Cloud token and replay
|
|
236
|
-
|
|
237
|
-
The npm CLI uses the same Rust engine as the public async SDK:
|
|
238
|
-
|
|
239
|
-
```bash
|
|
240
|
-
# Shell-safe token export (fails if ai-hist token fails)
|
|
241
|
-
RTH_TOKEN="$(ai-hist token)" || { echo "Failed to get token" >&2; exit 1; }
|
|
242
|
-
export RTH_TOKEN
|
|
243
|
-
ai-hist replay SESSION_ID
|
|
244
|
-
ai-hist replay SESSION_ID --json --out transcript.json
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
```ts
|
|
248
|
-
import { accessToken, replay } from 'ai-hist';
|
|
249
|
-
|
|
250
|
-
const token = await accessToken(); // Secret: do not log it.
|
|
251
|
-
const result = await replay('SESSION_ID', { json: true });
|
|
252
|
-
const events = JSON.parse(result.transcript!);
|
|
253
|
-
await replay('SESSION_ID', { json: true, out: 'transcript.json' });
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
Both APIs accept `baseUrl` (`--base-url` in the CLI) for explicit stage selection.
|
|
257
|
-
Token refresh and persistence run in Rust before returning a token with at least
|
|
258
|
-
60 seconds of recorded validity. Piped `token` stdout contains only the token
|
|
259
|
-
and one newline; failures leave stdout empty. Replay fetches every page in
|
|
260
|
-
server order; `limit` is the page size, and `maxContent` caps each event's content.
|
|
261
|
-
File output replaces its destination atomically only after all pages succeed.
|
|
262
|
-
Neither command opens or imports into the local history database.
|
|
208
|
+
## Optional cloud services
|
|
209
|
+
|
|
210
|
+
Use a `HistoryDestination` plugin for any service or pipe the public NDJSON
|
|
211
|
+
export to your own program. The local package has no cloud exports, login CLI,
|
|
212
|
+
or default cloud MCP tool. RelayHistory's auth, sharing, replay, durable upload
|
|
213
|
+
and readback live in [`@agent-relay/relayhistory`](../plugins/relayhistory/sdk/README.md).
|
|
214
|
+
Move imports from `ai-hist/cloud` to that package. Git hooks and commit linking
|
|
215
|
+
remain local SDK operations.
|
|
216
|
+
|
|
217
|
+
## Export and durable delivery
|
|
218
|
+
|
|
219
|
+
Use `exportHistory(selection)` for a bounded historical snapshot or
|
|
220
|
+
`ai-hist export --selection selection.json` for NDJSON stdout. Explicitly enabled
|
|
221
|
+
delivery jobs use `createHistoryDelivery`, `HistoryPluginRegistry`, and
|
|
222
|
+
`drainHistoryDelivery`/`runHistoryDelivery`. The same Rust queue handles one-shot
|
|
223
|
+
and background runs, immutable retries, exact acknowledgments, and worker leases.
|
|
224
|
+
Native contract 14 is required. See [delivery setup and contracts](../docs/history-delivery.md).
|
|
225
|
+
|
|
226
|
+
Source discovery and hydration accept `acquisitionTimeoutMs` for each selected
|
|
227
|
+
connector operation, including a complete paginated snapshot. The default is
|
|
228
|
+
300,000 ms; choose an integer from 1 through 3,600,000 ms. CLI acquisitions accept
|
|
229
|
+
`--acquisition-timeout-ms N`; MCP acquisitions accept `acquisition_timeout_ms`.
|
|
230
|
+
The same budget and cancellation signal reach the optional source helper.
|
|
231
|
+
Timeouts return `SOURCE_ACQUISITION_TIMEOUT`, cancellation returns
|
|
232
|
+
`SOURCE_ACQUISITION_CANCELLED`, and neither commits a partial snapshot. Typed
|
|
233
|
+
source failures such as `AUTHENTICATION_EXPIRED` and `SESSION_NOT_FOUND` retain
|
|
234
|
+
their public classes/codes with sanitized messages.
|
|
235
|
+
|
|
236
|
+
To remove a persistent delivery exclusion, cancel affected delivery jobs first,
|
|
237
|
+
clear the exclusion, then create new jobs to backfill the skipped history. A
|
|
238
|
+
running or paused generation cannot rewind revisions it already skipped;
|
|
239
|
+
attempting this returns `DELIVERY_GENERATION_REQUIRED`. Jobs whose selection
|
|
240
|
+
permanently excludes that session or cannot include it may continue.
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { discoverSessions, ensureLocalStore, formatSessionRow, getSession, getSessionEventsPage, getSessionFileEditsPage, getSessionRelationships, getSessionToolCallsPage, getSessionTree, hydrateSession, listSessionCatalogPage,
|
|
4
|
-
import {
|
|
5
|
-
const BOOLEAN_FLAGS = new Set(['all', 'fts', 'help', 'json', 'local', 'no-bootstrap', 'no-related', 'no-warning', 'once', 'pretty', 'remote', 'version']);
|
|
3
|
+
import { discoverSessions, ensureLocalStore, formatSessionRow, getSession, getSessionEventsPage, getSessionFileEditsPage, getSessionRelationships, getSessionToolCallsPage, getSessionTree, hydrateSession, listSessionCatalogPage, recent, resumeCommand, search, stats, sync, } from './index.js';
|
|
4
|
+
import { runDeliveryCommand, runHistoryExportCommand, loadHistoryApplicationConfig } from './delivery-cli.js';
|
|
5
|
+
const BOOLEAN_FLAGS = new Set(['all', 'fts', 'help', 'json', 'local', 'no-bootstrap', 'no-related', 'no-source-connectors', 'no-warning', 'once', 'pretty', 'remote', 'version']);
|
|
6
6
|
const VALUE_FLAGS = new Set([
|
|
7
|
-
'base-url', 'interval', 'label', 'max-content', 'out', 'after', 'after-ms', 'after-session-id', 'after-source', 'before-ms', 'db', 'limit',
|
|
8
|
-
'max-depth', 'max-nodes', 'project', 'source', 'tag', 'token', 'tokens',
|
|
7
|
+
'config', 'job', 'selection', 'poll-ms', 'timeout-ms', 'base-url', 'interval', 'label', 'max-content', 'out', 'after', 'after-ms', 'after-session-id', 'after-source', 'before-ms', 'db', 'limit',
|
|
8
|
+
'max-depth', 'max-nodes', 'config', 'source-connector', 'project', 'source', 'tag', 'token', 'tokens',
|
|
9
9
|
]);
|
|
10
10
|
const KNOWN_FLAGS = new Set([...BOOLEAN_FLAGS, ...VALUE_FLAGS]);
|
|
11
11
|
function versionTriple(value) {
|
|
@@ -101,21 +101,17 @@ function textFlag(args, name) {
|
|
|
101
101
|
const value = args.flags.get(name)?.at(-1);
|
|
102
102
|
return typeof value === 'string' ? value : undefined;
|
|
103
103
|
}
|
|
104
|
-
async function relayAccessTokenForCloudCommand(command, rawArgs, args, reuseStoredAuth) {
|
|
105
|
-
const explicitToken = textFlag(args, 'token');
|
|
106
|
-
if (explicitToken !== undefined)
|
|
107
|
-
return explicitToken;
|
|
108
|
-
const baseUrl = textFlag(args, 'base-url');
|
|
109
|
-
if (reuseStoredAuth && await loadStoredRelayhistoryAuth(baseUrl))
|
|
110
|
-
return undefined;
|
|
111
|
-
const preparedToken = await prepareCloudSessionForEnableCloud(command, rawArgs, process.env);
|
|
112
|
-
if (preparedToken !== null)
|
|
113
|
-
await validateCloudExchangeBaseUrl(baseUrl);
|
|
114
|
-
return preparedToken ?? undefined;
|
|
115
|
-
}
|
|
116
104
|
function textFlags(args, name) {
|
|
117
105
|
return (args.flags.get(name) ?? []).filter((value) => typeof value === 'string');
|
|
118
106
|
}
|
|
107
|
+
function sourceConnectorFlags(args) {
|
|
108
|
+
if (args.flags.has('no-source-connectors')) {
|
|
109
|
+
if (args.flags.has('source-connector'))
|
|
110
|
+
usage('--no-source-connectors and --source-connector are mutually exclusive');
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
return args.flags.has('source-connector') ? textFlags(args, 'source-connector') : undefined;
|
|
114
|
+
}
|
|
119
115
|
function numberFlag(args, name) {
|
|
120
116
|
const value = textFlag(args, name);
|
|
121
117
|
if (value === undefined)
|
|
@@ -204,10 +200,9 @@ function output(value, json) {
|
|
|
204
200
|
function showHelp() {
|
|
205
201
|
process.stdout.write(`Usage:
|
|
206
202
|
ai-hist [--no-bootstrap] [--db PATH] [--json] [--help]
|
|
207
|
-
ai-hist enable-cloud [--base-url URL] [--token TOKEN] [--db PATH] [--interval SECONDS] [--once] [--json]
|
|
208
203
|
ai-hist sessions list [--pretty] [--local | --remote | --all] [--source SOURCE]... [--limit N] [--before-ms MS] [--after JSON | --after-source SOURCE --after-session-id ID [--after-ms MS]] [--json]
|
|
209
|
-
ai-hist sessions discover [--local | --remote | --all] [--source SOURCE] [--limit N] [--json]
|
|
210
|
-
ai-hist sessions hydrate SOURCE SESSION_ID [--local | --remote | --all] [--no-related] [--db PATH] [--json]
|
|
204
|
+
ai-hist sessions discover [--local | --remote | --all] [--source-connector ID | --no-source-connectors] [--acquisition-timeout-ms N] [--source SOURCE] [--limit N] [--json]
|
|
205
|
+
ai-hist sessions hydrate SOURCE SESSION_ID [--local | --remote | --all] [--source-connector ID | --no-source-connectors] [--acquisition-timeout-ms N] [--no-related] [--db PATH] [--json]
|
|
211
206
|
ai-hist sessions relationships SOURCE SESSION_ID [--db PATH] [--json]
|
|
212
207
|
ai-hist sessions tree SOURCE SESSION_ID [--max-depth N] [--max-nodes N] [--db PATH] [--json]
|
|
213
208
|
ai-hist sessions tools SOURCE SESSION_ID [--limit N] [--after JSON] [--db PATH] [--json]
|
|
@@ -218,11 +213,12 @@ function showHelp() {
|
|
|
218
213
|
ai-hist events SESSION_ID [--source SOURCE] [--limit N] [--after JSON] [--json]
|
|
219
214
|
ai-hist resume QUERY... [--local | --remote | --all] [--db PATH] [--fts] [--json]
|
|
220
215
|
ai-hist pack QUERY... [--local | --remote | --all] [--source SOURCE] [--project PATH] [--tag TAG] [--limit N] [--tokens N] [--db PATH] [--fts] [--json]
|
|
221
|
-
ai-hist login [--base-url URL] [--token TOKEN] [--label LABEL] [--json]
|
|
222
|
-
ai-hist token [--base-url URL]
|
|
223
|
-
ai-hist replay SESSION_ID [--base-url URL] [--limit N] [--max-content N] [--json] [--out PATH] [--help]
|
|
224
216
|
ai-hist stats [--local | --remote | --all] [--json]
|
|
225
|
-
ai-hist
|
|
217
|
+
ai-hist export --selection FILE [--out FILE] [--db PATH]
|
|
218
|
+
ai-hist delivery enable|drain|run --config FILE [--job ID] [--db PATH]
|
|
219
|
+
ai-hist delivery status|pause|resume|retry|cancel [--job ID] [--db PATH]
|
|
220
|
+
ai-hist plugin COMMAND --config FILE -- [ARGS...]
|
|
221
|
+
ai-hist sync [--local | --remote | --all] [--source-connector ID | --no-source-connectors] [--acquisition-timeout-ms N] [--db PATH] [--json]
|
|
226
222
|
|
|
227
223
|
Every command that reads local history indexes it on first use; pass
|
|
228
224
|
--no-bootstrap to answer from the store exactly as it stands.
|
|
@@ -234,10 +230,9 @@ function usage(message) {
|
|
|
234
230
|
process.stderr.write(`ai-hist: ${message}\n\n`);
|
|
235
231
|
process.stderr.write(`Usage:
|
|
236
232
|
ai-hist [--no-bootstrap] [--db PATH] [--json] [--help]
|
|
237
|
-
ai-hist enable-cloud [--base-url URL] [--token TOKEN] [--db PATH] [--interval SECONDS] [--once] [--json]
|
|
238
233
|
ai-hist sessions list [--pretty] [--local | --remote | --all] [--source SOURCE]... [--limit N] [--before-ms MS] [--after JSON | --after-source SOURCE --after-session-id ID [--after-ms MS]] [--json]
|
|
239
|
-
ai-hist sessions discover [--local | --remote | --all] [--source SOURCE] [--limit N] [--json]
|
|
240
|
-
ai-hist sessions hydrate SOURCE SESSION_ID [--local | --remote | --all] [--no-related] [--db PATH] [--json]
|
|
234
|
+
ai-hist sessions discover [--local | --remote | --all] [--source-connector ID | --no-source-connectors] [--acquisition-timeout-ms N] [--source SOURCE] [--limit N] [--json]
|
|
235
|
+
ai-hist sessions hydrate SOURCE SESSION_ID [--local | --remote | --all] [--source-connector ID | --no-source-connectors] [--acquisition-timeout-ms N] [--no-related] [--db PATH] [--json]
|
|
241
236
|
ai-hist sessions relationships SOURCE SESSION_ID [--db PATH] [--json]
|
|
242
237
|
ai-hist sessions tree SOURCE SESSION_ID [--max-depth N] [--max-nodes N] [--db PATH] [--json]
|
|
243
238
|
ai-hist sessions tools SOURCE SESSION_ID [--limit N] [--after JSON] [--db PATH] [--json]
|
|
@@ -248,11 +243,12 @@ function usage(message) {
|
|
|
248
243
|
ai-hist events SESSION_ID [--source SOURCE] [--limit N] [--after JSON] [--json]
|
|
249
244
|
ai-hist resume QUERY... [--local | --remote | --all] [--db PATH] [--fts] [--json]
|
|
250
245
|
ai-hist pack QUERY... [--local | --remote | --all] [--source SOURCE] [--project PATH] [--tag TAG] [--limit N] [--tokens N] [--db PATH] [--fts] [--json]
|
|
251
|
-
ai-hist login [--base-url URL] [--token TOKEN] [--label LABEL] [--json]
|
|
252
|
-
ai-hist token [--base-url URL]
|
|
253
|
-
ai-hist replay SESSION_ID [--base-url URL] [--limit N] [--max-content N] [--json] [--out PATH] [--help]
|
|
254
246
|
ai-hist stats [--local | --remote | --all] [--json]
|
|
255
|
-
ai-hist
|
|
247
|
+
ai-hist export --selection FILE [--out FILE] [--db PATH]
|
|
248
|
+
ai-hist delivery enable|drain|run --config FILE [--job ID] [--db PATH]
|
|
249
|
+
ai-hist delivery status|pause|resume|retry|cancel [--job ID] [--db PATH]
|
|
250
|
+
ai-hist plugin COMMAND --config FILE -- [ARGS...]
|
|
251
|
+
ai-hist sync [--local | --remote | --all] [--source-connector ID | --no-source-connectors] [--acquisition-timeout-ms N] [--db PATH] [--json]
|
|
256
252
|
|
|
257
253
|
Every command that reads local history indexes it on first use; pass
|
|
258
254
|
--no-bootstrap to answer from the store exactly as it stands.
|
|
@@ -549,17 +545,11 @@ function validateInterval(args) {
|
|
|
549
545
|
// stops `search` and `ai-hist` disagreeing about whether a store exists.
|
|
550
546
|
const COMMANDS = new Map([
|
|
551
547
|
['', { name: 'ai-hist', positionals: [0, 0], allowed: ['db', 'json', 'help'], readsLocalStore: true }],
|
|
552
|
-
['
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
} }],
|
|
558
|
-
['token', { name: 'token', positionals: [0, 0], allowed: ['base-url'] }],
|
|
559
|
-
['replay', { name: 'replay', positionals: [1, 1], requires: 'replay requires SESSION_ID',
|
|
560
|
-
allowed: ['base-url', 'limit', 'max-content', 'json', 'out', 'help'] }],
|
|
561
|
-
['enable-cloud', { name: 'enable-cloud', positionals: [0, 0], validate: validateInterval,
|
|
562
|
-
allowed: ['base-url', 'db', 'interval', 'once', 'json', 'token'] }],
|
|
548
|
+
['export', { name: 'export', positionals: [0, 0], allowed: ['db', 'selection', 'out'] }],
|
|
549
|
+
['plugin', { name: 'plugin', positionals: [1, null], allowed: ['config'], requires: 'plugin requires a command name' }],
|
|
550
|
+
...['enable', 'status', 'drain', 'run', 'pause', 'resume', 'retry', 'cancel'].map((action) => [`delivery ${action}`, {
|
|
551
|
+
name: `delivery ${action}`, positionals: [0, 0], allowed: ['db', 'config', 'job', 'poll-ms', 'timeout-ms'],
|
|
552
|
+
}]),
|
|
563
553
|
['sessions list', { name: 'sessions list', positionals: [0, 0], readsLocalStore: true,
|
|
564
554
|
validate: (args) => {
|
|
565
555
|
if (args.flags.has('json') && args.flags.has('pretty'))
|
|
@@ -570,10 +560,12 @@ const COMMANDS = new Map([
|
|
|
570
560
|
'json', 'limit', 'local', 'pretty', 'remote', 'source',
|
|
571
561
|
] }],
|
|
572
562
|
['sessions discover', { name: 'sessions discover', positionals: [0, 0],
|
|
573
|
-
|
|
574
|
-
|
|
563
|
+
validate: (args) => { sourceConnectorFlags(args); },
|
|
564
|
+
allowed: ['all', 'db', 'json', 'limit', 'local', 'remote', 'source', 'config', 'source-connector', 'no-source-connectors', 'acquisition-timeout-ms'] }],
|
|
565
|
+
['sessions hydrate', { name: 'sessions hydrate', positionals: [2, 2],
|
|
575
566
|
requires: 'sessions hydrate requires SOURCE and SESSION_ID',
|
|
576
|
-
|
|
567
|
+
validate: (args) => { sourceConnectorFlags(args); },
|
|
568
|
+
allowed: ['all', 'db', 'json', 'local', 'no-bootstrap', 'no-related', 'remote', 'config', 'source-connector', 'no-source-connectors', 'acquisition-timeout-ms'] }],
|
|
577
569
|
['sessions relationships', { name: 'sessions relationships', positionals: [2, 2], readsLocalStore: true,
|
|
578
570
|
rejectsScope: true, requires: 'sessions relationships requires SOURCE and SESSION_ID',
|
|
579
571
|
allowed: ['all', 'db', 'json', 'local', 'remote'] }],
|
|
@@ -609,35 +601,36 @@ const COMMANDS = new Map([
|
|
|
609
601
|
allowed: ['all', 'db', 'json', 'local', 'remote', 'tag'] }],
|
|
610
602
|
// sync and `sessions discover` build the store rather than read it, so they
|
|
611
603
|
// do not bootstrap first; running them is itself the remedy for an empty one.
|
|
612
|
-
['sync', { name: 'sync', positionals: [0, 0],
|
|
604
|
+
['sync', { name: 'sync', positionals: [0, 0], validate: (args) => { sourceConnectorFlags(args); },
|
|
605
|
+
allowed: ['all', 'db', 'json', 'local', 'remote', 'config', 'source-connector', 'no-source-connectors', 'acquisition-timeout-ms'] }],
|
|
613
606
|
]);
|
|
614
607
|
/** Command words consumed before the positional arguments start. */
|
|
615
608
|
function commandWords(command) {
|
|
616
609
|
if (command === undefined)
|
|
617
610
|
return 0;
|
|
618
|
-
return command === 'sessions' ? 2 : 1;
|
|
611
|
+
return command === 'sessions' || command === 'delivery' ? 2 : 1;
|
|
619
612
|
}
|
|
620
613
|
function commandSpec(command, subcommand) {
|
|
621
614
|
if (command === undefined)
|
|
622
615
|
return COMMANDS.get('');
|
|
623
|
-
if (command === 'sessions')
|
|
624
|
-
return subcommand ? COMMANDS.get(
|
|
616
|
+
if (command === 'sessions' || command === 'delivery')
|
|
617
|
+
return subcommand ? COMMANDS.get(`${command} ${subcommand}`) : undefined;
|
|
625
618
|
return COMMANDS.get(command);
|
|
626
619
|
}
|
|
627
620
|
function unknownCommandMessage(command, subcommand) {
|
|
628
621
|
if (command === undefined)
|
|
629
622
|
return 'invalid usage';
|
|
630
|
-
if (command === 'sessions') {
|
|
623
|
+
if (command === 'sessions' || command === 'delivery') {
|
|
631
624
|
if (subcommand === undefined)
|
|
632
|
-
return
|
|
633
|
-
return `unknown
|
|
625
|
+
return `${command} requires a subcommand`;
|
|
626
|
+
return `unknown ${command} subcommand '${subcommand}'`;
|
|
634
627
|
}
|
|
635
628
|
return `unknown command '${command}'`;
|
|
636
629
|
}
|
|
637
630
|
// A store that was never built and a store holding no match are different
|
|
638
631
|
// answers, and `No results.` is only true of the second. Wording and exit code
|
|
639
632
|
// separate them; the query is not run against a store that cannot hold one.
|
|
640
|
-
/**
|
|
633
|
+
/** Cached remote evidence can answer an all-scope query even when local history is empty. */
|
|
641
634
|
function skipUnusableStoreGate(spec, scope) {
|
|
642
635
|
if (scope !== 'all')
|
|
643
636
|
return false;
|
|
@@ -668,7 +661,12 @@ async function main() {
|
|
|
668
661
|
await maybePrintUpdateNotice(version, rawArgs);
|
|
669
662
|
return;
|
|
670
663
|
}
|
|
671
|
-
const
|
|
664
|
+
const boundary = rawArgs.indexOf('--');
|
|
665
|
+
const beforeBoundary = boundary < 0 ? rawArgs : rawArgs.slice(0, boundary);
|
|
666
|
+
const separator = boundary >= 0 && parse(beforeBoundary).positional[0] === 'plugin' ? boundary : -1;
|
|
667
|
+
const pluginArgs = separator < 0 ? [] : rawArgs.slice(separator + 1);
|
|
668
|
+
const coreArgs = separator < 0 ? rawArgs : rawArgs.slice(0, separator);
|
|
669
|
+
const args = parse(coreArgs.map((arg) => arg === '-h' ? '--help' : arg));
|
|
672
670
|
const [command, subcommand, ...rest] = args.positional;
|
|
673
671
|
const json = args.flags.has('json');
|
|
674
672
|
// Handle help before command resolution to prevent unknown command errors
|
|
@@ -678,7 +676,7 @@ async function main() {
|
|
|
678
676
|
if (command === 'help' && subcommand === undefined && rest.length === 0) {
|
|
679
677
|
showHelp();
|
|
680
678
|
}
|
|
681
|
-
if (command === 'sessions' && subcommand === undefined && args.flags.has('help')) {
|
|
679
|
+
if ((command === 'sessions' || command === 'delivery') && subcommand === undefined && args.flags.has('help')) {
|
|
682
680
|
showHelp();
|
|
683
681
|
}
|
|
684
682
|
const spec = commandSpec(command, subcommand);
|
|
@@ -704,6 +702,30 @@ async function main() {
|
|
|
704
702
|
const sessionId = command === 'sessions' ? tail[1] : undefined;
|
|
705
703
|
const recentFallback = command === 'recent' && tail.length > 0 ? Number(tail[0]) : undefined;
|
|
706
704
|
const scope = scopeFlag(args);
|
|
705
|
+
if (command === 'delivery') {
|
|
706
|
+
await runDeliveryCommand(subcommand, { dbPath: textFlag(args, 'db'), configPath: textFlag(args, 'config'),
|
|
707
|
+
jobId: textFlag(args, 'job'), pollIntervalMs: numberFlag(args, 'poll-ms'), requestTimeoutMs: numberFlag(args, 'timeout-ms') });
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (command === 'export') {
|
|
711
|
+
const selectionPath = textFlag(args, 'selection');
|
|
712
|
+
if (!selectionPath)
|
|
713
|
+
usage('export requires --selection FILE');
|
|
714
|
+
await runHistoryExportCommand({ dbPath: textFlag(args, 'db'), selectionPath, outputPath: textFlag(args, 'out') });
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (command === 'plugin') {
|
|
718
|
+
const configPath = textFlag(args, 'config');
|
|
719
|
+
if (!configPath)
|
|
720
|
+
usage('plugin requires --config FILE');
|
|
721
|
+
const { registry } = await loadHistoryApplicationConfig(configPath);
|
|
722
|
+
const operation = registry.command(tail[0]);
|
|
723
|
+
if (!operation)
|
|
724
|
+
usage('configured plugin command not found');
|
|
725
|
+
output(await operation.run([...tail.slice(1), ...pluginArgs]), true);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const acquisitionPlugins = ['sync', 'sessions'].includes(command ?? '') && textFlag(args, 'config') ? (await loadHistoryApplicationConfig(textFlag(args, 'config'))).registry : undefined;
|
|
707
729
|
let readiness = null;
|
|
708
730
|
if (spec.readsLocalStore) {
|
|
709
731
|
readiness = await ensureLocalStore({
|
|
@@ -734,53 +756,6 @@ async function main() {
|
|
|
734
756
|
}
|
|
735
757
|
return;
|
|
736
758
|
}
|
|
737
|
-
if (command === 'login') {
|
|
738
|
-
const relayAccessToken = await relayAccessTokenForCloudCommand(command, rawArgs, args, false);
|
|
739
|
-
const auth = await login({
|
|
740
|
-
baseUrl: textFlag(args, 'base-url'),
|
|
741
|
-
relayAccessToken,
|
|
742
|
-
label: textFlag(args, 'label'),
|
|
743
|
-
});
|
|
744
|
-
if (json)
|
|
745
|
-
output({ ok: true, baseUrl: auth.baseUrl }, true);
|
|
746
|
-
else
|
|
747
|
-
process.stdout.write(`Logged in to ${auth.baseUrl} (session stored).\n`);
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
if (command === 'token') {
|
|
751
|
-
const token = await accessToken({ baseUrl: textFlag(args, 'base-url') });
|
|
752
|
-
if (process.stdout.isTTY)
|
|
753
|
-
process.stderr.write('Warning: this access token is a secret and will remain in terminal scrollback.\n');
|
|
754
|
-
process.stdout.write(`${token}\n`);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
if (command === 'replay') {
|
|
758
|
-
const result = await replay(subcommand, {
|
|
759
|
-
baseUrl: textFlag(args, 'base-url'), limit: nonNegativeIntFlag(args, 'limit'),
|
|
760
|
-
maxContent: nonNegativeIntFlag(args, 'max-content'), json, out: textFlag(args, 'out'),
|
|
761
|
-
});
|
|
762
|
-
if (result.transcript !== null)
|
|
763
|
-
process.stdout.write(result.transcript);
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
if (command === 'enable-cloud') {
|
|
767
|
-
const relayAccessToken = await relayAccessTokenForCloudCommand(command, rawArgs, args, true);
|
|
768
|
-
const handle = await enableCloud({
|
|
769
|
-
baseUrl: textFlag(args, 'base-url'), dbPath: textFlag(args, 'db'),
|
|
770
|
-
relayAccessToken,
|
|
771
|
-
intervalMs: intervalSeconds * 1000,
|
|
772
|
-
watch: !args.flags.has('once'),
|
|
773
|
-
onPush: (result) => output(result, json),
|
|
774
|
-
});
|
|
775
|
-
const { stop, ...result } = handle;
|
|
776
|
-
output(result, json);
|
|
777
|
-
if (!args.flags.has('once')) {
|
|
778
|
-
const shutdown = () => { void stop(); };
|
|
779
|
-
process.once('SIGINT', shutdown);
|
|
780
|
-
process.once('SIGTERM', shutdown);
|
|
781
|
-
}
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
759
|
if (command === 'sessions' && subcommand === 'list') {
|
|
785
760
|
const sources = textFlags(args, 'source');
|
|
786
761
|
const page = await listSessionCatalogPage({
|
|
@@ -803,6 +778,7 @@ async function main() {
|
|
|
803
778
|
if (command === 'sessions' && subcommand === 'discover') {
|
|
804
779
|
const sources = textFlags(args, 'source');
|
|
805
780
|
outputDiscovery(await discoverSessions({
|
|
781
|
+
sourceConnectors: sourceConnectorFlags(args), acquisitionTimeoutMs: numberFlag(args, 'acquisition-timeout-ms'), plugins: acquisitionPlugins,
|
|
806
782
|
dbPath: textFlag(args, 'db'), scope: scopeFlag(args), sources: sources.length ? sources : undefined,
|
|
807
783
|
limit: numberFlag(args, 'limit'),
|
|
808
784
|
}), json);
|
|
@@ -810,6 +786,7 @@ async function main() {
|
|
|
810
786
|
}
|
|
811
787
|
if (command === 'sessions' && subcommand === 'hydrate') {
|
|
812
788
|
outputHydration(await hydrateSession({
|
|
789
|
+
sourceConnectors: sourceConnectorFlags(args), acquisitionTimeoutMs: numberFlag(args, 'acquisition-timeout-ms'), plugins: acquisitionPlugins,
|
|
813
790
|
source: sessionSource,
|
|
814
791
|
sessionId: sessionId,
|
|
815
792
|
scope: scopeFlag(args),
|
|
@@ -878,7 +855,7 @@ async function main() {
|
|
|
878
855
|
return;
|
|
879
856
|
}
|
|
880
857
|
if (command === 'sync') {
|
|
881
|
-
output(await sync({ dbPath: textFlag(args, 'db'), scope: scopeFlag(args) }), json);
|
|
858
|
+
output(await sync({ dbPath: textFlag(args, 'db'), scope: scopeFlag(args), sourceConnectors: sourceConnectorFlags(args), acquisitionTimeoutMs: numberFlag(args, 'acquisition-timeout-ms'), plugins: acquisitionPlugins }), json);
|
|
882
859
|
return;
|
|
883
860
|
}
|
|
884
861
|
usage();
|