@adrive/cli 0.3.0 → 1.1.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.
@@ -1,210 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join, basename } from 'node:path';
4
- import kleur from 'kleur';
5
- import { CliApiClient } from '../lib/api-client.js';
6
- import { requireLogin, configDir } from '../lib/config.js';
7
- import { encryptContentAsync } from '../lib/crypto.js';
8
- import { redact, summarizeHits } from '../lib/redactor.js';
9
- function manifestPath() {
10
- return join(configDir(), 'transcripts.manifest.json');
11
- }
12
- async function readManifest() {
13
- try {
14
- const raw = await fs.readFile(manifestPath(), 'utf8');
15
- return JSON.parse(raw);
16
- }
17
- catch {
18
- return { ingested: {} };
19
- }
20
- }
21
- async function writeManifest(m) {
22
- await fs.mkdir(configDir(), { recursive: true, mode: 0o700 });
23
- await fs.writeFile(manifestPath(), JSON.stringify(m, null, 2), { encoding: 'utf8', mode: 0o600 });
24
- }
25
- async function findClaudeTranscripts() {
26
- const projectsDir = join(homedir(), '.claude', 'projects');
27
- let projects;
28
- try {
29
- projects = await fs.readdir(projectsDir);
30
- }
31
- catch {
32
- return [];
33
- }
34
- const out = [];
35
- for (const p of projects) {
36
- const pdir = join(projectsDir, p);
37
- let files;
38
- try {
39
- files = await fs.readdir(pdir);
40
- }
41
- catch {
42
- continue;
43
- }
44
- for (const f of files) {
45
- if (!f.endsWith('.jsonl'))
46
- continue;
47
- const full = join(pdir, f);
48
- let stat;
49
- try {
50
- stat = await fs.stat(full);
51
- }
52
- catch {
53
- continue;
54
- }
55
- out.push({
56
- sessionId: f.replace(/\.jsonl$/, ''),
57
- path: full,
58
- mtime: stat.mtime,
59
- projectDir: p,
60
- });
61
- }
62
- }
63
- return out.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
64
- }
65
- function extractTurn(line) {
66
- let obj;
67
- try {
68
- obj = JSON.parse(line);
69
- }
70
- catch {
71
- return null;
72
- }
73
- const type = obj.type ?? '';
74
- const ts = obj.timestamp ?? undefined;
75
- if (type === 'user' || type === 'assistant') {
76
- const msg = obj.message;
77
- if (!msg)
78
- return null;
79
- const role = msg.role ?? type;
80
- const content = msg.content;
81
- let text = '';
82
- if (typeof content === 'string') {
83
- text = content;
84
- }
85
- else if (Array.isArray(content)) {
86
- for (const part of content) {
87
- if (typeof part === 'string')
88
- text += part;
89
- else if (part && typeof part === 'object' && 'text' in part) {
90
- text += String(part.text);
91
- }
92
- }
93
- }
94
- if (!text.trim())
95
- return null;
96
- return { role, text, ts };
97
- }
98
- return null;
99
- }
100
- function formatTranscript(turns) {
101
- const lines = [];
102
- for (const t of turns) {
103
- lines.push(`## ${t.role}${t.ts ? ` _(${t.ts})_` : ''}`);
104
- lines.push('');
105
- lines.push(t.text);
106
- lines.push('');
107
- }
108
- return lines.join('\n');
109
- }
110
- export async function cmdTranscriptsIngest(opts = {}) {
111
- const cfg = await requireLogin();
112
- if (!cfg.preferences?.capture_transcripts && !opts.dryRun) {
113
- process.stderr.write(`${kleur.yellow('[warn]')} Remember conversations is off in your preferences.\n` +
114
- ` Enable with \`adrive privacy --capture-transcripts on\`,\n` +
115
- ` or run a one-off dry-run with \`adrive transcripts ingest --dry-run\`.\n`);
116
- process.exit(2);
117
- }
118
- const platform = opts.platform ?? 'claude-code';
119
- if (platform !== 'claude-code') {
120
- throw new Error(`Transcript ingestion for ${platform} is not implemented yet.`);
121
- }
122
- const all = await findClaudeTranscripts();
123
- let entries = all;
124
- if (opts.since) {
125
- const since = new Date(opts.since);
126
- if (Number.isNaN(since.getTime()))
127
- throw new Error('Invalid --since date.');
128
- entries = entries.filter((e) => e.mtime > since);
129
- }
130
- if (typeof opts.limit === 'number')
131
- entries = entries.slice(0, opts.limit);
132
- if (entries.length === 0) {
133
- process.stdout.write('No transcripts found.\n');
134
- return;
135
- }
136
- const manifest = await readManifest();
137
- const client = new CliApiClient({
138
- baseUrl: cfg.api_url,
139
- apiKey: cfg.api_key,
140
- agentPlatform: cfg.agent_platform ?? 'cli',
141
- agentRole: cfg.agent_role,
142
- });
143
- let workspaceId = opts.workspace ?? cfg.default_workspace;
144
- if (!workspaceId && !opts.dryRun) {
145
- const list = await client.listWorkspaces();
146
- if (list.length === 1)
147
- workspaceId = list[0].id;
148
- else if (list.length === 0)
149
- throw new Error('No workspaces. Create one first.');
150
- else
151
- throw new Error('Multiple workspaces — specify --workspace <id>.');
152
- }
153
- const encrypt = !!cfg.preferences?.private_workspace;
154
- process.stdout.write(`${kleur.bold(`Found ${entries.length} ${platform} transcripts.`)}\n`);
155
- let ingested = 0;
156
- let skipped = 0;
157
- for (const e of entries) {
158
- if (!opts.reingest && manifest.ingested[e.sessionId]) {
159
- skipped++;
160
- continue;
161
- }
162
- let raw;
163
- try {
164
- raw = await fs.readFile(e.path, 'utf8');
165
- }
166
- catch {
167
- continue;
168
- }
169
- const turns = [];
170
- for (const line of raw.split(/\r?\n/)) {
171
- if (!line.trim())
172
- continue;
173
- const turn = extractTurn(line);
174
- if (turn)
175
- turns.push(turn);
176
- }
177
- if (turns.length === 0) {
178
- skipped++;
179
- continue;
180
- }
181
- const md = `# Transcript — ${platform} / ${e.sessionId}\n\n` +
182
- `Project: ${e.projectDir}\nLast modified: ${e.mtime.toISOString()}\nTurns: ${turns.length}\n\n` +
183
- formatTranscript(turns);
184
- const { redacted, hits } = redact(md);
185
- const remotePath = `transcripts/${platform}/${e.sessionId}.md`;
186
- const tag = summarizeHits(hits);
187
- if (opts.dryRun) {
188
- process.stdout.write(`${kleur.dim('[dry]')} ${remotePath} ${kleur.dim(`(${turns.length} turns, redactor: ${tag})`)}\n`);
189
- ingested++;
190
- continue;
191
- }
192
- try {
193
- const body = encrypt ? await encryptContentAsync(cfg.api_key, redacted) : redacted;
194
- await client.writeFile(workspaceId, remotePath, body, `transcript ingest (${tag})`);
195
- manifest.ingested[e.sessionId] = new Date().toISOString();
196
- ingested++;
197
- process.stdout.write(`${kleur.green('[ok]')} ${remotePath}` +
198
- (encrypt ? kleur.cyan(' (encrypted)') : '') +
199
- (hits.length ? kleur.yellow(` (redacted: ${tag})`) : '') +
200
- '\n');
201
- }
202
- catch (err) {
203
- const msg = err instanceof Error ? err.message : String(err);
204
- process.stderr.write(`${kleur.red('[err]')} ${basename(e.path)}: ${msg}\n`);
205
- }
206
- }
207
- if (!opts.dryRun)
208
- await writeManifest(manifest);
209
- process.stdout.write(`\nIngested ${ingested}, skipped ${skipped}.\n`);
210
- }