@adrive/cli 0.2.1 → 1.0.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 +1 -1
- package/dist/commands/context.js +18 -1
- package/dist/commands/init.js +8 -0
- package/dist/commands/pull.js +2 -2
- package/dist/commands/push.js +2 -2
- package/dist/commands/timeline.js +65 -0
- package/dist/commands/transcripts.js +56 -0
- package/dist/commands/watch.js +110 -0
- package/dist/index.js +44 -1
- package/dist/lib/api-client.js +15 -0
- package/dist/lib/crypto.js +48 -21
- package/dist/lib/ignore.js +137 -0
- package/dist/lib/keychain.js +93 -0
- package/dist/lib/redactor.js +69 -0
- package/dist/lib/skill.js +117 -0
- package/dist/lib/transcripts/claude.js +112 -0
- package/dist/lib/transcripts/codex.js +168 -0
- package/dist/lib/transcripts/cursor.js +23 -0
- package/dist/lib/transcripts/ingest.js +115 -0
- package/dist/lib/transcripts/types.js +10 -0
- package/package.json +3 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import kleur from 'kleur';
|
|
5
|
+
import { CliApiClient } from '../api-client.js';
|
|
6
|
+
import { configDir } from '../config.js';
|
|
7
|
+
import { encryptContentAsync } from '../crypto.js';
|
|
8
|
+
import { redact, summarizeHits } from '../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
|
+
function formatTranscript(turns) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
for (const t of turns) {
|
|
28
|
+
lines.push(`## ${t.role}${t.ts ? ` _(${t.ts})_` : ''}`);
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push(t.text);
|
|
31
|
+
lines.push('');
|
|
32
|
+
}
|
|
33
|
+
return lines.join('\n');
|
|
34
|
+
}
|
|
35
|
+
function buildMarkdown(platform, doc) {
|
|
36
|
+
return (`# Transcript — ${platform} / ${doc.entry.sessionId}\n\n` +
|
|
37
|
+
`Project: ${doc.entry.label}\nLast modified: ${doc.entry.mtime.toISOString()}\n` +
|
|
38
|
+
`Turns: ${doc.turns.length}\n\n` +
|
|
39
|
+
formatTranscript(doc.turns));
|
|
40
|
+
}
|
|
41
|
+
export async function ingestSource(source, cfg, opts = {}) {
|
|
42
|
+
const home = opts.home ?? homedir();
|
|
43
|
+
const since = opts.since ? new Date(opts.since) : undefined;
|
|
44
|
+
if (since && Number.isNaN(since.getTime())) {
|
|
45
|
+
throw new Error('Invalid --since date.');
|
|
46
|
+
}
|
|
47
|
+
let entries = await source.enumerate({ home, since });
|
|
48
|
+
if (typeof opts.limit === 'number')
|
|
49
|
+
entries = entries.slice(0, opts.limit);
|
|
50
|
+
if (entries.length === 0) {
|
|
51
|
+
process.stdout.write(`No ${source.platform} transcripts found.\n`);
|
|
52
|
+
return { platform: source.platform, ingested: 0, skipped: 0 };
|
|
53
|
+
}
|
|
54
|
+
const manifest = await readManifest();
|
|
55
|
+
const client = new CliApiClient({
|
|
56
|
+
baseUrl: cfg.api_url,
|
|
57
|
+
apiKey: cfg.api_key,
|
|
58
|
+
agentPlatform: cfg.agent_platform ?? 'cli',
|
|
59
|
+
agentRole: cfg.agent_role,
|
|
60
|
+
});
|
|
61
|
+
let workspaceId = opts.workspace ?? cfg.default_workspace;
|
|
62
|
+
if (!workspaceId && !opts.dryRun) {
|
|
63
|
+
const list = await client.listWorkspaces();
|
|
64
|
+
if (list.length === 1)
|
|
65
|
+
workspaceId = list[0].id;
|
|
66
|
+
else if (list.length === 0)
|
|
67
|
+
throw new Error('No workspaces. Create one first.');
|
|
68
|
+
else
|
|
69
|
+
throw new Error('Multiple workspaces — specify --workspace <id>.');
|
|
70
|
+
}
|
|
71
|
+
const encrypt = !!cfg.preferences?.private_workspace;
|
|
72
|
+
process.stdout.write(`${kleur.bold(`Found ${entries.length} ${source.platform} transcripts.`)}\n`);
|
|
73
|
+
let ingested = 0;
|
|
74
|
+
let skipped = 0;
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (!opts.reingest && manifest.ingested[entry.manifestKey]) {
|
|
77
|
+
skipped++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const doc = await source.parseSession(entry);
|
|
81
|
+
if (!doc) {
|
|
82
|
+
skipped++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const md = buildMarkdown(source.platform, doc);
|
|
86
|
+
const { redacted, hits } = redact(md);
|
|
87
|
+
const tag = summarizeHits(hits);
|
|
88
|
+
const remotePath = `transcripts/${source.platform}/${entry.sessionId}.md`;
|
|
89
|
+
if (opts.dryRun) {
|
|
90
|
+
process.stdout.write(`${kleur.dim('[dry]')} ${remotePath} ${kleur.dim(`(${doc.turns.length} turns, redactor: ${tag})`)}\n`);
|
|
91
|
+
ingested++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const body = encrypt ? await encryptContentAsync(cfg.api_key, redacted) : redacted;
|
|
96
|
+
await client.writeFile(workspaceId, remotePath, body, `transcript ingest (${tag})`);
|
|
97
|
+
manifest.ingested[entry.manifestKey] = new Date().toISOString();
|
|
98
|
+
ingested++;
|
|
99
|
+
process.stdout.write(`${kleur.green('[ok]')} ${remotePath}` +
|
|
100
|
+
(encrypt ? kleur.cyan(' (encrypted)') : '') +
|
|
101
|
+
(hits.length ? kleur.yellow(` (redacted: ${tag})`) : '') +
|
|
102
|
+
'\n');
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
const base = entry.path.split('/').pop() ?? entry.path;
|
|
107
|
+
process.stderr.write(`${kleur.red('[err]')} ${base}: ${msg}\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!opts.dryRun)
|
|
111
|
+
await writeManifest(manifest);
|
|
112
|
+
process.stdout.write(`\nIngested ${ingested}, skipped ${skipped}.\n`);
|
|
113
|
+
return { platform: source.platform, ingested, skipped };
|
|
114
|
+
}
|
|
115
|
+
export { buildMarkdown, formatTranscript };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Functional TranscriptSource interface — per eng-review decision E4.
|
|
2
|
+
//
|
|
3
|
+
// A source knows two things:
|
|
4
|
+
// 1. `enumerate(home, opts)` — find candidate session files on disk
|
|
5
|
+
// 2. `parseSession(entry)` — turn one session file into a TranscriptDoc
|
|
6
|
+
//
|
|
7
|
+
// Everything else (redact, encrypt, manifest, upload) is centralized in
|
|
8
|
+
// lib/transcripts/ingest.ts so each source stays small and the cross-cutting
|
|
9
|
+
// concerns can't drift between platforms.
|
|
10
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adrive/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AgentDrive CLI —
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AgentDrive CLI — cross-agent artifact workspace with provenance and portable storage across Claude Code, Codex, Cursor, and LangGraph.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"test": "vitest run"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
+
"chokidar": "^5.0.0",
|
|
47
48
|
"commander": "^12.1.0",
|
|
48
49
|
"kleur": "^4.1.5",
|
|
49
50
|
"open": "^10.1.0",
|