@lifestreamdynamics/vault-cli 1.3.9 → 1.3.10

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.
@@ -5,6 +5,30 @@ import { createOutput, handleError } from '../utils/output.js';
5
5
  export function registerAiCommands(program) {
6
6
  const ai = program.command('ai').description('AI chat and document summarization');
7
7
  const sessions = ai.command('sessions').description('AI chat session management');
8
+ addGlobalFlags(sessions.command('create')
9
+ .description('Create a new AI chat session')
10
+ .option('--title <title>', 'Session title')
11
+ .option('--vault <vaultId>', 'Vault ID to scope the session'))
12
+ .action(async (_opts) => {
13
+ const flags = resolveFlags(_opts);
14
+ const out = createOutput(flags);
15
+ out.startSpinner('Creating AI session...');
16
+ try {
17
+ const client = await getClientAsync();
18
+ const session = await client.ai.createSession({
19
+ title: _opts.title ? String(_opts.title) : undefined,
20
+ vaultId: _opts.vault ? String(_opts.vault) : undefined,
21
+ });
22
+ out.success(`Session created: ${session.id}`, {
23
+ id: session.id,
24
+ title: session.title ?? 'Untitled',
25
+ vaultId: session.vaultId ?? null,
26
+ });
27
+ }
28
+ catch (err) {
29
+ handleError(out, err, 'Failed to create AI session');
30
+ }
31
+ });
8
32
  addGlobalFlags(sessions.command('list')
9
33
  .description('List AI chat sessions'))
10
34
  .action(async (_opts) => {
@@ -91,8 +91,8 @@ EXAMPLES
91
91
  }
92
92
  });
93
93
  addGlobalFlags(audit.command('export')
94
- .description('Export audit log entries to a CSV file or stdout')
95
- .option('--format <format>', 'Export format (csv)', 'csv')
94
+ .description('Export audit log entries to a CSV or JSON file or stdout')
95
+ .option('--format <format>', 'Export format (csv or json)', 'csv')
96
96
  .option('--file <file>', 'Output file path')
97
97
  .option('--status <code>', 'Filter by HTTP status code', parseInt)
98
98
  .option('--since <date>', 'Show entries since date (ISO 8601)')
@@ -102,8 +102,8 @@ EXAMPLES
102
102
  const flags = resolveFlags(_opts);
103
103
  const out = createOutput(flags);
104
104
  try {
105
- if (_opts.format !== 'csv') {
106
- out.error(`Unsupported format: ${String(_opts.format)}. Only 'csv' is supported.`);
105
+ if (_opts.format !== 'csv' && _opts.format !== 'json') {
106
+ out.error(`Unsupported format: ${String(_opts.format)}. Supported: csv, json`);
107
107
  process.exitCode = 2;
108
108
  return;
109
109
  }
@@ -141,6 +141,26 @@ EXAMPLES
141
141
  out.status('No audit log entries to export.');
142
142
  return;
143
143
  }
144
+ if (_opts.format === 'json') {
145
+ const jsonOutput = JSON.stringify(entries, null, 2);
146
+ if (_opts.file) {
147
+ const outputPath = String(_opts.file);
148
+ const outputDir = path.dirname(outputPath);
149
+ if (!fs.existsSync(outputDir)) {
150
+ fs.mkdirSync(outputDir, { recursive: true });
151
+ }
152
+ fs.writeFileSync(outputPath, jsonOutput, 'utf-8');
153
+ out.success(`Exported ${entries.length} entries to ${outputPath}`, {
154
+ entries: entries.length,
155
+ path: outputPath,
156
+ format: 'json',
157
+ });
158
+ }
159
+ else {
160
+ out.raw(jsonOutput + '\n');
161
+ }
162
+ return;
163
+ }
144
164
  const csv = logger.exportCsv(entries);
145
165
  if (_opts.file) {
146
166
  const outputPath = String(_opts.file);
@@ -100,9 +100,37 @@ export function registerLinkCommands(program) {
100
100
  process.stdout.write(JSON.stringify({ nodes: graph.nodes, edges: graph.edges }) + '\n');
101
101
  }
102
102
  else {
103
- process.stdout.write(chalk.bold(`Nodes: ${graph.nodes.length} Edges: ${graph.edges.length}\n`));
103
+ process.stdout.write(chalk.bold(`Nodes: ${graph.nodes.length} Edges: ${graph.edges.length}\n\n`));
104
+ // Most connected nodes (top 5)
105
+ const connectionCounts = new Map();
104
106
  for (const node of graph.nodes) {
105
- process.stdout.write(` ${chalk.cyan(String(node.path ?? node.id))}\n`);
107
+ connectionCounts.set(node.id, 0);
108
+ }
109
+ for (const edge of graph.edges) {
110
+ connectionCounts.set(edge.source, (connectionCounts.get(edge.source) ?? 0) + 1);
111
+ connectionCounts.set(edge.target, (connectionCounts.get(edge.target) ?? 0) + 1);
112
+ }
113
+ const sorted = [...connectionCounts.entries()].sort((a, b) => b[1] - a[1]);
114
+ const topConnected = sorted.slice(0, 5).filter(([, count]) => count > 0);
115
+ if (topConnected.length > 0) {
116
+ process.stdout.write(chalk.bold('Most connected:\n'));
117
+ for (const [nodeId, count] of topConnected) {
118
+ const node = graph.nodes.find((n) => n.id === nodeId);
119
+ process.stdout.write(` ${chalk.cyan(String(node?.path ?? nodeId))} (${count} links)\n`);
120
+ }
121
+ process.stdout.write('\n');
122
+ }
123
+ // Orphan nodes (no connections)
124
+ const orphans = sorted.filter(([, count]) => count === 0);
125
+ if (orphans.length > 0) {
126
+ process.stdout.write(chalk.bold(`Orphan nodes (${orphans.length}):\n`));
127
+ for (const [nodeId] of orphans.slice(0, 10)) {
128
+ const node = graph.nodes.find((n) => n.id === nodeId);
129
+ process.stdout.write(` ${chalk.dim(String(node?.path ?? nodeId))}\n`);
130
+ }
131
+ if (orphans.length > 10) {
132
+ process.stdout.write(chalk.dim(` ... and ${orphans.length - 10} more\n`));
133
+ }
106
134
  }
107
135
  }
108
136
  }
@@ -164,13 +164,16 @@ export function registerPublishCommands(program) {
164
164
  const result = await client.publish.getSubdomain(vaultId);
165
165
  out.stopSpinner();
166
166
  if (flags.output === 'json') {
167
- out.record({ subdomain: result.subdomain });
167
+ out.raw(JSON.stringify(result, null, 2) + '\n');
168
168
  }
169
169
  else if (result.subdomain == null) {
170
170
  out.status('No subdomain configured.');
171
171
  }
172
172
  else {
173
- out.record({ subdomain: result.subdomain });
173
+ out.record({
174
+ subdomain: result.subdomain,
175
+ url: `https://${result.subdomain}.lifestreamdynamics.com`,
176
+ });
174
177
  }
175
178
  }
176
179
  catch (err) {
@@ -169,10 +169,12 @@ EXAMPLES
169
169
  // vault tree
170
170
  addGlobalFlags(vaults.command('tree')
171
171
  .description('Show vault file tree')
172
- .argument('<vaultId>', 'Vault ID or slug'))
172
+ .argument('<vaultId>', 'Vault ID or slug')
173
+ .option('--depth <n>', 'Maximum display depth (0 = root only)', parseInt))
173
174
  .action(async (vaultId, _opts) => {
174
175
  const flags = resolveFlags(_opts);
175
176
  const out = createOutput(flags);
177
+ const maxDepth = _opts.depth;
176
178
  out.startSpinner('Fetching vault tree...');
177
179
  try {
178
180
  vaultId = await resolveVaultId(vaultId);
@@ -184,6 +186,8 @@ EXAMPLES
184
186
  }
185
187
  else {
186
188
  function printNode(node, depth) {
189
+ if (maxDepth !== undefined && depth > maxDepth)
190
+ return;
187
191
  const indent = ' '.repeat(depth);
188
192
  const icon = node.type === 'directory' ? chalk.yellow('📁') : chalk.cyan('📄');
189
193
  process.stdout.write(`${indent}${icon} ${node.name}\n`);
@@ -146,7 +146,8 @@ EXAMPLES
146
146
  .argument('<version>', 'Version number to restore')
147
147
  .addHelpText('after', `
148
148
  EXAMPLES
149
- lsvault versions restore abc123 notes/todo.md 2`))
149
+ lsvault versions restore abc123 notes/todo.md 2
150
+ lsvault versions restore abc123 notes/todo.md 2 --dry-run`))
150
151
  .action(async (vaultId, docPath, versionStr, _opts) => {
151
152
  const flags = resolveFlags(_opts);
152
153
  const out = createOutput(flags);
@@ -156,6 +157,31 @@ EXAMPLES
156
157
  process.exitCode = 1;
157
158
  return;
158
159
  }
160
+ if (flags.dryRun) {
161
+ out.startSpinner(`Fetching version ${versionNum} preview...`);
162
+ try {
163
+ vaultId = await resolveVaultId(vaultId);
164
+ const client = await getClientAsync();
165
+ const version = await client.documents.getVersion(vaultId, docPath, versionNum);
166
+ out.stopSpinner();
167
+ if (flags.output === 'json') {
168
+ out.raw(JSON.stringify({ dryRun: true, version: { version: version.versionNum, createdAt: version.createdAt, size: version.content?.length ?? 0 } }, null, 2) + '\n');
169
+ }
170
+ else {
171
+ process.stdout.write(chalk.bold('Dry run — no changes made\n\n'));
172
+ process.stdout.write(`Version: ${version.versionNum}\n`);
173
+ process.stdout.write(`Created: ${version.createdAt}\n`);
174
+ if (version.content) {
175
+ const preview = version.content.slice(0, 200);
176
+ process.stdout.write(`Content preview:\n${chalk.dim(preview)}${version.content.length > 200 ? '...' : ''}\n`);
177
+ }
178
+ }
179
+ }
180
+ catch (err) {
181
+ handleError(out, err, 'Failed to preview version');
182
+ }
183
+ return;
184
+ }
159
185
  out.startSpinner(`Restoring to version ${versionNum}...`);
160
186
  try {
161
187
  vaultId = await resolveVaultId(vaultId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifestreamdynamics/vault-cli",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
4
4
  "description": "Command-line interface for Lifestream Vault",
5
5
  "engines": {
6
6
  "node": ">=22"