@ivotoby/postgram-cli 1.30.0 → 1.31.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/dist/client.js CHANGED
@@ -49,6 +49,12 @@ export function createPgmClient(options) {
49
49
  body: input
50
50
  });
51
51
  },
52
+ groomSessionContext(input) {
53
+ return request(options, '/api/memory/session-context/groom', {
54
+ method: 'POST',
55
+ body: input
56
+ });
57
+ },
52
58
  recallEntity(id, input = {}) {
53
59
  const params = new URLSearchParams();
54
60
  if (input.owner) {
package/dist/pgm.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
- import { createWriteStream } from 'node:fs';
3
+ import { createWriteStream, readFileSync } from 'node:fs';
4
4
  import { mkdir, readFile as fsReadFile, stat } from 'node:fs/promises';
5
5
  import path from 'node:path';
6
6
  import { Command } from 'commander';
@@ -10,6 +10,11 @@ import { handleCliFailure, isJsonMode, parseCommaList, parseJsonObject, printHum
10
10
  import { AppError, ErrorCode } from './errors.js';
11
11
  import { buildSyncManifest } from './sync-walk.js';
12
12
  import { compactEdgeResponse, compactEntityListResponse, compactGraphResponse, compactSearchResponse, compactStoredEntityResponse, entityListResponseToToon, graphResponseToToon, searchResponseToToon } from './search-output.js';
13
+ function readCliVersion() {
14
+ const rawPackageJson = readFileSync(new URL('../package.json', import.meta.url), 'utf8');
15
+ const packageJson = JSON.parse(rawPackageJson);
16
+ return packageJson.version;
17
+ }
13
18
  function formatStoredEntity(entity) {
14
19
  return [
15
20
  `${entity.type} ${shortId(entity.id)}${entity.status ? ` [${entity.status}]` : ''}`,
@@ -64,6 +69,43 @@ function validateToonOptions(options, json) {
64
69
  throw new AppError(ErrorCode.VALIDATION, '--toon cannot be combined with --json or --full-response');
65
70
  }
66
71
  }
72
+ function parseDurationMs(value) {
73
+ const match = /^(\d+)([mhd])$/i.exec(value.trim());
74
+ if (!match) {
75
+ throw new AppError(ErrorCode.VALIDATION, `Invalid duration '${value}'. Use format like '15m', '2h', or '7d'.`);
76
+ }
77
+ const [, amountText, unitText] = match;
78
+ const amount = Number(amountText);
79
+ const unit = unitText.toLowerCase();
80
+ const milliseconds = unit === 'm'
81
+ ? amount * 60 * 1000
82
+ : unit === 'h'
83
+ ? amount * 60 * 60 * 1000
84
+ : amount * 24 * 60 * 60 * 1000;
85
+ if (milliseconds > 3650 * 24 * 60 * 60 * 1000) {
86
+ throw new AppError(ErrorCode.VALIDATION, `Duration '${value}' exceeds the maximum allowed (3650 days / ~10 years).`);
87
+ }
88
+ return milliseconds;
89
+ }
90
+ function formatGroomCandidates(candidates) {
91
+ if (candidates.length === 0) {
92
+ return ['No eligible session-context memories'];
93
+ }
94
+ const lines = [`Would archive ${candidates.length} session-context memories`];
95
+ for (const candidate of candidates) {
96
+ const metadata = candidate.metadata;
97
+ lines.push([
98
+ ` ${shortId(candidate.id)} ${candidate.createdAt.slice(0, 10)}`,
99
+ candidate.tags.length ? `tags=${candidate.tags.join(',')}` : 'tags=-',
100
+ metadata.topic ? `topic=${metadata.topic}` : undefined,
101
+ metadata.session_id ? `session=${metadata.session_id}` : undefined,
102
+ candidate.content ? `content=${candidate.content}` : 'content=-'
103
+ ]
104
+ .filter(Boolean)
105
+ .join(' | '));
106
+ }
107
+ return lines;
108
+ }
67
109
  async function resolveStoreContent(content) {
68
110
  if (content !== undefined) {
69
111
  return content;
@@ -202,6 +244,7 @@ const program = new Command();
202
244
  program
203
245
  .name('pgm')
204
246
  .description('Postgram human CLI')
247
+ .version(readCliVersion())
205
248
  .option('--json', 'emit JSON output');
206
249
  program
207
250
  .command('store')
@@ -259,7 +302,6 @@ program
259
302
  .option('--expand-graph', 'include graph-connected entities in results')
260
303
  .option('--include-archived', 'include archived entities in results')
261
304
  .option('--memory-role <role>', 'memory role filter: durable_memory or session_context')
262
- .option('--include-other-clients-session-context', 'include session context from other clients')
263
305
  .option('--full-response', 'emit the full API response instead of compact default output when used with --json')
264
306
  .option('--toon', 'emit compact TOON output for lower agent token use; formatting is applied in the CLI, not the API')
265
307
  .action(async (query, options, command) => {
@@ -282,8 +324,7 @@ program
282
324
  recency_weight: Number(options.recencyWeight),
283
325
  expand_graph: options.expandGraph === true ? true : undefined,
284
326
  include_archived: options.includeArchived === true ? true : undefined,
285
- memory_role: options.memoryRole,
286
- include_other_clients_session_context: options.includeOtherClientsSessionContext === true ? true : undefined
327
+ memory_role: options.memoryRole
287
328
  });
288
329
  if (options.toon === true) {
289
330
  return searchResponseToToon(compactSearchResponse(body));
@@ -299,6 +340,50 @@ program
299
340
  const memoryCommand = program
300
341
  .command('memory')
301
342
  .description('Memory-specific commands');
343
+ memoryCommand
344
+ .command('groom')
345
+ .description('Preview or archive stale session-context memories for the authenticated client')
346
+ .option('--dry-run', 'preview without mutating')
347
+ .option('--older-than <duration>', 'only include memories older than this (e.g. 15m, 2h, 7d)', '7d')
348
+ .option('--limit <limit>', 'maximum candidates', '50')
349
+ .option('--topic <topic>', 'filter by topic')
350
+ .option('--session-id <sessionId>', 'filter by session id')
351
+ .option('--tag <tag>', 'filter by tag (repeatable)', (value, previous = []) => {
352
+ previous.push(value);
353
+ return previous;
354
+ }, [])
355
+ .option('--yes', 'confirm archive mutation')
356
+ .action(async (options, command) => {
357
+ await runWithClient(command, async (client, json) => {
358
+ const limit = Number.parseInt(options.limit, 10);
359
+ if (!Number.isInteger(limit) || limit <= 0) {
360
+ throw new AppError(ErrorCode.VALIDATION, '--limit must be a positive integer');
361
+ }
362
+ if (options.dryRun !== true && options.yes !== true) {
363
+ throw new AppError(ErrorCode.VALIDATION, '--yes is required outside dry-run');
364
+ }
365
+ const body = await client.groomSessionContext({
366
+ dry_run: options.dryRun === true,
367
+ confirmed: options.dryRun === true ? undefined : true,
368
+ older_than_ms: parseDurationMs(options.olderThan),
369
+ limit,
370
+ topic: options.topic,
371
+ session_id: options.sessionId,
372
+ tags: options.tag
373
+ });
374
+ if (json) {
375
+ return body;
376
+ }
377
+ if (body.dryRun) {
378
+ return formatGroomCandidates(body.eligible ?? []);
379
+ }
380
+ return [
381
+ `Archived ${body.archived} session-context memories`,
382
+ ` promoted: ${body.promoted}`,
383
+ ` skipped: ${body.skipped}`
384
+ ];
385
+ });
386
+ });
302
387
  memoryCommand
303
388
  .command('session-context')
304
389
  .alias('session')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ivotoby/postgram-cli",
3
- "version": "1.30.0",
3
+ "version": "1.31.0",
4
4
  "description": "Postgram CLI — store, search, and manage entities from the command line",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,6 +33,7 @@
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc -p tsconfig.build.json",
36
+ "typecheck": "tsc -p tsconfig.build.json --noEmit",
36
37
  "prepublishOnly": "npm run build",
37
38
  "test": "vitest run",
38
39
  "test:watch": "vitest",