imprint-mcp 0.2.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/CHANGELOG.md +168 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/examples/discoverandgo/README.md +57 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/cron.json +8 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/index.ts +89 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +39 -0
- package/examples/echo/README.md +37 -0
- package/examples/echo/echo_test/index.ts +31 -0
- package/examples/google-flights/search_google_flights/index.ts +101 -0
- package/examples/google-flights/search_google_flights/parser.test.ts +140 -0
- package/examples/google-flights/search_google_flights/parser.ts +189 -0
- package/examples/google-flights/search_google_flights/playbook.yaml +130 -0
- package/examples/google-flights/search_google_flights/workflow.json +48 -0
- package/examples/google-hotels/search_google_hotels/index.ts +194 -0
- package/examples/google-hotels/search_google_hotels/parser.test.ts +168 -0
- package/examples/google-hotels/search_google_hotels/parser.ts +330 -0
- package/examples/google-hotels/search_google_hotels/playbook.yaml +125 -0
- package/examples/google-hotels/search_google_hotels/workflow.json +111 -0
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +144 -0
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +380 -0
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +50 -0
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +136 -0
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +97 -0
- package/examples/southwest/README.md +81 -0
- package/examples/southwest/search_southwest_flights/backends.json +23 -0
- package/examples/southwest/search_southwest_flights/cron.json +19 -0
- package/examples/southwest/search_southwest_flights/index.ts +110 -0
- package/examples/southwest/search_southwest_flights/playbook.yaml +46 -0
- package/examples/southwest/search_southwest_flights/workflow.json +54 -0
- package/package.json +78 -0
- package/prompts/compile-agent.md +580 -0
- package/prompts/intent-detection.md +198 -0
- package/prompts/playbook-compilation.md +279 -0
- package/prompts/request-triage.md +74 -0
- package/prompts/tool-candidate-detection.md +104 -0
- package/src/cli.ts +1287 -0
- package/src/imprint/agent.ts +468 -0
- package/src/imprint/app-api-hosts.ts +53 -0
- package/src/imprint/backend-ladder.ts +568 -0
- package/src/imprint/check.ts +136 -0
- package/src/imprint/chromium.ts +211 -0
- package/src/imprint/claude-cli-compile.ts +640 -0
- package/src/imprint/cli-credential.ts +394 -0
- package/src/imprint/codex-cli-compile.ts +712 -0
- package/src/imprint/compile-agent-types.ts +40 -0
- package/src/imprint/compile-agent.ts +404 -0
- package/src/imprint/compile-tools.ts +1389 -0
- package/src/imprint/compile.ts +720 -0
- package/src/imprint/cookie-jar.ts +246 -0
- package/src/imprint/credential-bundle.ts +195 -0
- package/src/imprint/credential-extract.ts +290 -0
- package/src/imprint/credential-store.ts +707 -0
- package/src/imprint/cron.ts +312 -0
- package/src/imprint/doctor.ts +223 -0
- package/src/imprint/emit.ts +154 -0
- package/src/imprint/etld.ts +134 -0
- package/src/imprint/freeform-redact.ts +216 -0
- package/src/imprint/inject-listener.ts +137 -0
- package/src/imprint/install.ts +795 -0
- package/src/imprint/integrations.ts +385 -0
- package/src/imprint/is-compiled.ts +2 -0
- package/src/imprint/json-path.ts +100 -0
- package/src/imprint/llm.ts +998 -0
- package/src/imprint/load-json.ts +54 -0
- package/src/imprint/log.ts +33 -0
- package/src/imprint/login.ts +166 -0
- package/src/imprint/mcp-compile-server.ts +282 -0
- package/src/imprint/mcp-maintenance.ts +1790 -0
- package/src/imprint/mcp-server.ts +350 -0
- package/src/imprint/multi-progress.ts +69 -0
- package/src/imprint/notify.ts +155 -0
- package/src/imprint/paths.ts +64 -0
- package/src/imprint/playbook-parser.ts +21 -0
- package/src/imprint/playbook-runner.ts +465 -0
- package/src/imprint/probe-backends.ts +251 -0
- package/src/imprint/progress.ts +28 -0
- package/src/imprint/record.ts +470 -0
- package/src/imprint/redact.ts +550 -0
- package/src/imprint/replay-capture.ts +387 -0
- package/src/imprint/request-context.ts +66 -0
- package/src/imprint/runtime-link.ts +73 -0
- package/src/imprint/runtime.ts +942 -0
- package/src/imprint/sensitive-keys.ts +156 -0
- package/src/imprint/session-diff.ts +409 -0
- package/src/imprint/session-merge.ts +198 -0
- package/src/imprint/session-writer.ts +149 -0
- package/src/imprint/sites.ts +27 -0
- package/src/imprint/stealth-fetch.ts +434 -0
- package/src/imprint/teach-state.ts +235 -0
- package/src/imprint/teach.ts +2120 -0
- package/src/imprint/tool-candidates.ts +423 -0
- package/src/imprint/tool-loader.ts +186 -0
- package/src/imprint/tool-selection.ts +70 -0
- package/src/imprint/tracing.ts +508 -0
- package/src/imprint/types.ts +472 -0
- package/src/imprint/version.ts +21 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `imprint credential ...` — surface for managing the local credential
|
|
3
|
+
* manager: list / get / set / delete, plus encrypted bundle export/import
|
|
4
|
+
* for laptop → remote-agent sharing, plus a one-shot migration from the
|
|
5
|
+
* legacy JSON store.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import * as p from '@clack/prompts';
|
|
10
|
+
import { type BundleEnvelope, exportBundle, importBundle } from './credential-bundle.ts';
|
|
11
|
+
import {
|
|
12
|
+
getCredentialBackend,
|
|
13
|
+
legacyStorePath,
|
|
14
|
+
listLegacyStoreSites,
|
|
15
|
+
listManifestSites,
|
|
16
|
+
markLegacyStoreMigrated,
|
|
17
|
+
readLegacyStore,
|
|
18
|
+
readSiteManifest,
|
|
19
|
+
removeManifestEntry,
|
|
20
|
+
upsertManifestEntry,
|
|
21
|
+
} from './credential-store.ts';
|
|
22
|
+
|
|
23
|
+
interface ParsedArgs {
|
|
24
|
+
positionals: string[];
|
|
25
|
+
flags: Record<string, string | boolean>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseSubArgs(argv: string[]): ParsedArgs {
|
|
29
|
+
const positionals: string[] = [];
|
|
30
|
+
const flags: Record<string, string | boolean> = {};
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const a = argv[i] ?? '';
|
|
33
|
+
if (a.startsWith('--')) {
|
|
34
|
+
const eq = a.indexOf('=');
|
|
35
|
+
if (eq !== -1) {
|
|
36
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
37
|
+
} else {
|
|
38
|
+
const next = argv[i + 1];
|
|
39
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
40
|
+
flags[a.slice(2)] = next;
|
|
41
|
+
i++;
|
|
42
|
+
} else {
|
|
43
|
+
flags[a.slice(2)] = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
positionals.push(a);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { positionals, flags };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CREDENTIAL_HELP = `imprint credential — manage local credential storage
|
|
54
|
+
|
|
55
|
+
USAGE
|
|
56
|
+
imprint credential list [<site>]
|
|
57
|
+
imprint credential get <site> <name> --reveal
|
|
58
|
+
imprint credential set <site> <name>
|
|
59
|
+
imprint credential delete <site> <name>
|
|
60
|
+
imprint credential export <site> [--out <path>]
|
|
61
|
+
imprint credential import <site> <bundle-path>
|
|
62
|
+
imprint credential migrate
|
|
63
|
+
|
|
64
|
+
DESCRIPTION
|
|
65
|
+
Credentials live in the OS keychain (when available) or a libsodium-encrypted
|
|
66
|
+
file at ~/.config/imprint/secrets.enc. Skill folders never carry plaintext
|
|
67
|
+
values — only a credentials.manifest.json that lists the names a downstream
|
|
68
|
+
agent (e.g., OpenClaw / Hermes) needs to provision.
|
|
69
|
+
|
|
70
|
+
To share a skill with a remote agent:
|
|
71
|
+
1. Ship the skill folder via git or any other channel.
|
|
72
|
+
2. On the agent, either:
|
|
73
|
+
a) Re-enter credentials interactively:
|
|
74
|
+
imprint credential set <site> username
|
|
75
|
+
imprint credential set <site> password
|
|
76
|
+
b) Or import an encrypted bundle from the laptop:
|
|
77
|
+
(laptop) imprint credential export <site> --out bundle.imprintbundle
|
|
78
|
+
(agent) imprint credential import <site> bundle.imprintbundle
|
|
79
|
+
|
|
80
|
+
EXAMPLES
|
|
81
|
+
imprint credential list southwest-seats
|
|
82
|
+
imprint credential set southwest-seats password
|
|
83
|
+
imprint credential export southwest-seats --out /tmp/sw.imprintbundle
|
|
84
|
+
`;
|
|
85
|
+
|
|
86
|
+
export async function runCredentialCommand(argv: string[]): Promise<number> {
|
|
87
|
+
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
|
|
88
|
+
console.log(CREDENTIAL_HELP);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sub = argv[0] ?? '';
|
|
93
|
+
const rest = argv.slice(1);
|
|
94
|
+
|
|
95
|
+
switch (sub) {
|
|
96
|
+
case 'list':
|
|
97
|
+
return await cmdList(rest);
|
|
98
|
+
case 'get':
|
|
99
|
+
return await cmdGet(rest);
|
|
100
|
+
case 'set':
|
|
101
|
+
return await cmdSet(rest);
|
|
102
|
+
case 'delete':
|
|
103
|
+
case 'rm':
|
|
104
|
+
return await cmdDelete(rest);
|
|
105
|
+
case 'export':
|
|
106
|
+
return await cmdExport(rest);
|
|
107
|
+
case 'import':
|
|
108
|
+
return await cmdImport(rest);
|
|
109
|
+
case 'migrate':
|
|
110
|
+
return await cmdMigrate();
|
|
111
|
+
default:
|
|
112
|
+
console.error(
|
|
113
|
+
`error: unknown subcommand 'credential ${sub}' — run \`imprint credential --help\``,
|
|
114
|
+
);
|
|
115
|
+
return 2;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function cmdList(argv: string[]): Promise<number> {
|
|
120
|
+
const { positionals } = parseSubArgs(argv);
|
|
121
|
+
const site = positionals[0];
|
|
122
|
+
|
|
123
|
+
const backend = await getCredentialBackend();
|
|
124
|
+
|
|
125
|
+
if (!site) {
|
|
126
|
+
const manifestSites = listManifestSites();
|
|
127
|
+
const backendSites = await backend.listSites();
|
|
128
|
+
const legacy = listLegacyStoreSites();
|
|
129
|
+
const all = Array.from(new Set([...manifestSites, ...backendSites, ...legacy])).sort();
|
|
130
|
+
if (all.length === 0) {
|
|
131
|
+
console.log('No sites have stored credentials yet.');
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
console.log(`Stored credentials (backend: ${backend.id}):`);
|
|
135
|
+
for (const s of all) {
|
|
136
|
+
const m = readSiteManifest(s);
|
|
137
|
+
const names = m?.secrets.map((e) => e.name) ?? (await backend.listSecrets(s));
|
|
138
|
+
const cookies = await backend.getCookies(s);
|
|
139
|
+
const legacyTag = legacy.includes(s) ? ' (legacy)' : '';
|
|
140
|
+
console.log(
|
|
141
|
+
` ${s}${legacyTag} — ${names.length} secret${names.length === 1 ? '' : 's'} (${names.join(', ') || '–'}), ${cookies.length} cookie${cookies.length === 1 ? '' : 's'}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const m = readSiteManifest(site);
|
|
148
|
+
const backendNames = await backend.listSecrets(site);
|
|
149
|
+
const cookies = await backend.getCookies(site);
|
|
150
|
+
const legacyExists = readLegacyStore(site) !== null;
|
|
151
|
+
|
|
152
|
+
if (!m && backendNames.length === 0 && cookies.length === 0 && !legacyExists) {
|
|
153
|
+
console.log(`No credentials stored for "${site}".`);
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`Credentials for "${site}" (backend: ${backend.id}):`);
|
|
158
|
+
if (m) {
|
|
159
|
+
for (const entry of m.secrets) {
|
|
160
|
+
const has = backendNames.includes(entry.name);
|
|
161
|
+
console.log(
|
|
162
|
+
` ${entry.name} [${entry.kind}]${has ? '' : ' ⚠ missing in backend'}${entry.description ? ` — ${entry.description}` : ''}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
for (const n of backendNames) console.log(` ${n}`);
|
|
167
|
+
}
|
|
168
|
+
if (cookies.length > 0) {
|
|
169
|
+
console.log(
|
|
170
|
+
` cookies: ${cookies.length} stored (${cookies
|
|
171
|
+
.slice(0, 4)
|
|
172
|
+
.map((c) => c.name)
|
|
173
|
+
.join(', ')}${cookies.length > 4 ? '…' : ''})`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (legacyExists) {
|
|
177
|
+
console.log(
|
|
178
|
+
` ⚠ legacy JSON store at ${legacyStorePath(site)} — run \`imprint credential migrate\` to move into the backend.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function cmdGet(argv: string[]): Promise<number> {
|
|
185
|
+
const { positionals, flags } = parseSubArgs(argv);
|
|
186
|
+
const [site, name] = positionals;
|
|
187
|
+
if (!site || !name) {
|
|
188
|
+
console.error('error: usage: imprint credential get <site> <name> --reveal');
|
|
189
|
+
return 2;
|
|
190
|
+
}
|
|
191
|
+
if (flags.reveal !== true) {
|
|
192
|
+
console.error(
|
|
193
|
+
'error: `imprint credential get` requires --reveal to print the value.\n This is a guardrail against accidental disclosure (shoulder-surfing, screenshots, logs).',
|
|
194
|
+
);
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
const backend = await getCredentialBackend();
|
|
198
|
+
const v = await backend.getSecret(site, name);
|
|
199
|
+
if (v === null) {
|
|
200
|
+
console.error(`error: no secret "${name}" stored for site "${site}".`);
|
|
201
|
+
return 1;
|
|
202
|
+
}
|
|
203
|
+
process.stdout.write(v);
|
|
204
|
+
process.stdout.write('\n');
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function cmdSet(argv: string[]): Promise<number> {
|
|
209
|
+
const { positionals } = parseSubArgs(argv);
|
|
210
|
+
const [site, name] = positionals;
|
|
211
|
+
if (!site || !name) {
|
|
212
|
+
console.error('error: usage: imprint credential set <site> <name>');
|
|
213
|
+
return 2;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const value = await p.password({
|
|
217
|
+
message: `Value for ${site}/${name}`,
|
|
218
|
+
mask: '*',
|
|
219
|
+
validate: (v) => (!v || v.length === 0 ? 'Cannot be empty.' : undefined),
|
|
220
|
+
});
|
|
221
|
+
if (p.isCancel(value)) {
|
|
222
|
+
p.outro('Cancelled.');
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const backend = await getCredentialBackend();
|
|
227
|
+
await backend.setSecret(site, name, value as string);
|
|
228
|
+
|
|
229
|
+
// Update the manifest so `list` and downstream consumers see this entry.
|
|
230
|
+
const existing = readSiteManifest(site);
|
|
231
|
+
const existingEntry = existing?.secrets.find((s) => s.name === name);
|
|
232
|
+
upsertManifestEntry(site, {
|
|
233
|
+
name,
|
|
234
|
+
kind: existingEntry?.kind ?? guessKind(name),
|
|
235
|
+
description: existingEntry?.description,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log(`[imprint] saved ${site}/${name} (backend: ${backend.id})`);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function guessKind(name: string): 'username' | 'password' | 'email' | 'token' | 'opaque' {
|
|
243
|
+
const n = name.toLowerCase();
|
|
244
|
+
if (n.includes('password') || n.includes('passwd') || n === 'pwd') return 'password';
|
|
245
|
+
if (n.includes('email')) return 'email';
|
|
246
|
+
if (n.includes('user') || n === 'login' || n.includes('account')) return 'username';
|
|
247
|
+
if (n.includes('token') || n.includes('apikey') || n.includes('api_key')) return 'token';
|
|
248
|
+
return 'opaque';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function cmdDelete(argv: string[]): Promise<number> {
|
|
252
|
+
const { positionals } = parseSubArgs(argv);
|
|
253
|
+
const [site, name] = positionals;
|
|
254
|
+
if (!site || !name) {
|
|
255
|
+
console.error('error: usage: imprint credential delete <site> <name>');
|
|
256
|
+
return 2;
|
|
257
|
+
}
|
|
258
|
+
const backend = await getCredentialBackend();
|
|
259
|
+
await backend.deleteSecret(site, name);
|
|
260
|
+
removeManifestEntry(site, name);
|
|
261
|
+
console.log(`[imprint] deleted ${site}/${name}`);
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function cmdExport(argv: string[]): Promise<number> {
|
|
266
|
+
const { positionals, flags } = parseSubArgs(argv);
|
|
267
|
+
const site = positionals[0];
|
|
268
|
+
if (!site) {
|
|
269
|
+
console.error('error: usage: imprint credential export <site> [--out <path>]');
|
|
270
|
+
return 2;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const passphrase = await p.password({
|
|
274
|
+
message: 'Passphrase to encrypt the bundle (≥ 8 chars)',
|
|
275
|
+
mask: '*',
|
|
276
|
+
validate: (v) => (!v || v.length < 8 ? 'Passphrase must be at least 8 characters.' : undefined),
|
|
277
|
+
});
|
|
278
|
+
if (p.isCancel(passphrase)) {
|
|
279
|
+
p.outro('Cancelled.');
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
const confirm = await p.password({
|
|
283
|
+
message: 'Confirm passphrase',
|
|
284
|
+
mask: '*',
|
|
285
|
+
});
|
|
286
|
+
if (p.isCancel(confirm)) {
|
|
287
|
+
p.outro('Cancelled.');
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
if (confirm !== passphrase) {
|
|
291
|
+
console.error('error: passphrases do not match.');
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const backend = await getCredentialBackend();
|
|
296
|
+
const envelope = await exportBundle({
|
|
297
|
+
backend,
|
|
298
|
+
site,
|
|
299
|
+
passphrase: passphrase as string,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const outPath = (flags.out as string) ?? `${site}.imprintbundle`;
|
|
303
|
+
writeFileSync(outPath, JSON.stringify(envelope, null, 2), 'utf8');
|
|
304
|
+
console.log(`[imprint] bundle → ${outPath}`);
|
|
305
|
+
console.log(
|
|
306
|
+
`[imprint] transfer this file to the consuming agent (any channel — it's encrypted), then run:`,
|
|
307
|
+
);
|
|
308
|
+
console.log(` imprint credential import ${site} ${outPath}`);
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function cmdImport(argv: string[]): Promise<number> {
|
|
313
|
+
const { positionals } = parseSubArgs(argv);
|
|
314
|
+
const [site, bundlePath] = positionals;
|
|
315
|
+
if (!site || !bundlePath) {
|
|
316
|
+
console.error('error: usage: imprint credential import <site> <bundle-path>');
|
|
317
|
+
return 2;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let envelope: BundleEnvelope;
|
|
321
|
+
try {
|
|
322
|
+
envelope = JSON.parse(readFileSync(bundlePath, 'utf8')) as BundleEnvelope;
|
|
323
|
+
} catch (err) {
|
|
324
|
+
console.error(
|
|
325
|
+
`error: cannot read bundle "${bundlePath}": ${err instanceof Error ? err.message : String(err)}`,
|
|
326
|
+
);
|
|
327
|
+
return 1;
|
|
328
|
+
}
|
|
329
|
+
if (envelope.site !== site) {
|
|
330
|
+
console.error(
|
|
331
|
+
`error: bundle is for site "${envelope.site}" but you specified "${site}". Re-run with the correct site, or rename via \`imprint credential import ${envelope.site} …\`.`,
|
|
332
|
+
);
|
|
333
|
+
return 1;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const passphrase = await p.password({
|
|
337
|
+
message: 'Bundle passphrase',
|
|
338
|
+
mask: '*',
|
|
339
|
+
});
|
|
340
|
+
if (p.isCancel(passphrase)) {
|
|
341
|
+
p.outro('Cancelled.');
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const backend = await getCredentialBackend();
|
|
346
|
+
let result: { imported: string[]; cookieCount: number; storageCount: number };
|
|
347
|
+
try {
|
|
348
|
+
result = await importBundle({
|
|
349
|
+
backend,
|
|
350
|
+
envelope,
|
|
351
|
+
passphrase: passphrase as string,
|
|
352
|
+
});
|
|
353
|
+
} catch (err) {
|
|
354
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
355
|
+
return 1;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log(
|
|
359
|
+
`[imprint] imported ${result.imported.length} secret(s) (${result.imported.join(', ') || '–'}), ${result.cookieCount} cookie(s), and ${result.storageCount} storage value(s) for "${site}"`,
|
|
360
|
+
);
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function cmdMigrate(): Promise<number> {
|
|
365
|
+
const sites = listLegacyStoreSites();
|
|
366
|
+
if (sites.length === 0) {
|
|
367
|
+
console.log('Nothing to migrate — no legacy ~/.config/imprint/credentials/*.json files found.');
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
const backend = await getCredentialBackend();
|
|
371
|
+
console.log(`Migrating ${sites.length} site(s) to backend: ${backend.id}`);
|
|
372
|
+
for (const site of sites) {
|
|
373
|
+
const legacy = readLegacyStore(site);
|
|
374
|
+
if (!legacy) continue;
|
|
375
|
+
let count = 0;
|
|
376
|
+
for (const [name, value] of Object.entries(legacy.values)) {
|
|
377
|
+
await backend.setSecret(site, name, value);
|
|
378
|
+
upsertManifestEntry(site, {
|
|
379
|
+
name,
|
|
380
|
+
kind: guessKind(name),
|
|
381
|
+
description: 'Migrated from legacy JSON store',
|
|
382
|
+
});
|
|
383
|
+
count++;
|
|
384
|
+
}
|
|
385
|
+
if (legacy.cookies.length > 0) {
|
|
386
|
+
await backend.setCookies(site, legacy.cookies);
|
|
387
|
+
}
|
|
388
|
+
markLegacyStoreMigrated(site);
|
|
389
|
+
console.log(
|
|
390
|
+
` ${site}: ${count} secret${count === 1 ? '' : 's'}, ${legacy.cookies.length} cookie${legacy.cookies.length === 1 ? '' : 's'}`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
return 0;
|
|
394
|
+
}
|