@learncard/cli 3.6.8 → 4.0.1
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 +85 -0
- package/README.md +36 -12
- package/dist/index.js +6673 -2038
- package/package.json +27 -15
- package/project.json +0 -42
- package/rollup.config.js +0 -34
- package/scripts/embed-snippets.ts +0 -47
- package/src/auth-grant.test.ts +0 -54
- package/src/auth-grant.ts +0 -34
- package/src/clr/validate.test.ts +0 -65
- package/src/clr/validate.ts +0 -242
- package/src/clr.ts +0 -119
- package/src/consent-contract.ts +0 -116
- package/src/demo-inbox-refresh.test.ts +0 -737
- package/src/demo-inbox-refresh.ts +0 -804
- package/src/demo-refresh-command.test.ts +0 -57
- package/src/demo-refresh-command.ts +0 -22
- package/src/demo-refresh-ui.test.ts +0 -66
- package/src/demo-refresh-ui.ts +0 -65
- package/src/demo-refresh.test.ts +0 -140
- package/src/demo-refresh.ts +0 -309
- package/src/doctor/checks.test.ts +0 -448
- package/src/doctor/checks.ts +0 -497
- package/src/doctor.test.ts +0 -67
- package/src/doctor.ts +0 -118
- package/src/embed.ts +0 -102
- package/src/inbox.test.ts +0 -257
- package/src/inbox.ts +0 -221
- package/src/index.tsx +0 -876
- package/src/init.ts +0 -26
- package/src/open.test.ts +0 -40
- package/src/open.ts +0 -186
- package/src/org/apply.test.ts +0 -1108
- package/src/org/apply.ts +0 -924
- package/src/org/branding.test.ts +0 -60
- package/src/org/diff.ts +0 -14
- package/src/org/load.ts +0 -50
- package/src/org/schema.test.ts +0 -256
- package/src/org/schema.ts +0 -216
- package/src/org.ts +0 -124
- package/src/out.test.ts +0 -40
- package/src/out.ts +0 -16
- package/src/phase-two.test.ts +0 -154
- package/src/project.test.ts +0 -232
- package/src/project.ts +0 -489
- package/src/promote.test.ts +0 -142
- package/src/promote.ts +0 -202
- package/src/random.ts +0 -3
- package/src/refresh.test.ts +0 -86
- package/src/refresh.ts +0 -93
- package/src/replHelpers.test.ts +0 -80
- package/src/replHelpers.ts +0 -45
- package/src/revoke.test.ts +0 -29
- package/src/revoke.ts +0 -65
- package/src/send-template.test.ts +0 -20
- package/src/send.test.ts +0 -321
- package/src/send.ts +0 -373
- package/src/setup-signing.test.ts +0 -62
- package/src/setup-signing.ts +0 -185
- package/src/snippet-files.ts +0 -16
- package/src/status.test.ts +0 -33
- package/src/status.ts +0 -94
- package/src/token.test.ts +0 -28
- package/src/token.ts +0 -139
- package/src/types.d.ts +0 -17
- package/src/verify.test.ts +0 -35
- package/src/verify.ts +0 -66
- package/src/webhook.ts +0 -247
- package/src/whoami.test.ts +0 -67
- package/src/whoami.ts +0 -129
- package/tsconfig.json +0 -38
- package/vitest.config.ts +0 -8
package/src/index.tsx
DELETED
|
@@ -1,876 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import dns from 'node:dns';
|
|
3
|
-
import { createInterface } from 'node:readline/promises';
|
|
4
|
-
import { Writable } from 'node:stream';
|
|
5
|
-
import { inspect } from 'node:util';
|
|
6
|
-
|
|
7
|
-
import { getTestCache } from '@learncard/core';
|
|
8
|
-
import { initLearnCard, emptyLearnCard, learnCardFromSeed } from '@learncard/init';
|
|
9
|
-
import { getLCAPlugin } from '@learncard/lca-api-plugin';
|
|
10
|
-
import { openBadgeV2Plugin } from '@learncard/open-badge-v2-plugin';
|
|
11
|
-
import * as types from '@learncard/types';
|
|
12
|
-
import { getLinkedClaimsPlugin } from '@learncard/linked-claims-plugin';
|
|
13
|
-
import gradient from 'gradient-string';
|
|
14
|
-
import figlet from 'figlet';
|
|
15
|
-
import { Option, program } from 'commander';
|
|
16
|
-
import clipboard from 'clipboardy';
|
|
17
|
-
|
|
18
|
-
import { getLerRsPlugin } from '@learncard/ler-rs-plugin';
|
|
19
|
-
import { getRenderMethodPlugin } from '@learncard/render-method-plugin';
|
|
20
|
-
|
|
21
|
-
import { generateRandomSeed } from './random';
|
|
22
|
-
import { runSend } from './send';
|
|
23
|
-
import { out } from './out';
|
|
24
|
-
import {
|
|
25
|
-
createLearnCardBundle,
|
|
26
|
-
exportLearnCardBundle as writeLearnCardBundle,
|
|
27
|
-
importLearnCardBundle,
|
|
28
|
-
readLearnCardBundle,
|
|
29
|
-
restoreLearnCardFromBundle as restoreBundle,
|
|
30
|
-
} from '@learncard/holder-continuity';
|
|
31
|
-
import {
|
|
32
|
-
createExportLearnCardBundleHelper,
|
|
33
|
-
createRestoreLearnCardFromBundleHelper,
|
|
34
|
-
} from './replHelpers';
|
|
35
|
-
import { registerOrgCommand } from './org';
|
|
36
|
-
import { registerDoctorCommand } from './doctor';
|
|
37
|
-
import { registerClrCommand } from './clr';
|
|
38
|
-
import { registerInboxCommand } from './inbox';
|
|
39
|
-
import { registerRefreshCommand } from './refresh';
|
|
40
|
-
import { registerWhoamiCommand } from './whoami';
|
|
41
|
-
import { registerPromoteCommand } from './promote';
|
|
42
|
-
|
|
43
|
-
import packageJson from '../package.json';
|
|
44
|
-
|
|
45
|
-
dns.setDefaultResultOrder('ipv4first');
|
|
46
|
-
|
|
47
|
-
type CliGlobals = {
|
|
48
|
-
seed: string;
|
|
49
|
-
generateRandomSeed: typeof generateRandomSeed;
|
|
50
|
-
emptyLearnCard: typeof emptyLearnCard;
|
|
51
|
-
learnCardFromSeed: typeof learnCardFromSeed;
|
|
52
|
-
initLearnCard: typeof initLearnCard;
|
|
53
|
-
learnCard: any;
|
|
54
|
-
types: typeof types;
|
|
55
|
-
getTestCache: typeof getTestCache;
|
|
56
|
-
copy: typeof copyFunction;
|
|
57
|
-
getLearnCardBundlePassword: typeof getLearnCardBundlePassword;
|
|
58
|
-
exportLearnCardBundle: ReturnType<typeof createExportLearnCardBundleHelper>;
|
|
59
|
-
importLearnCardBundle: typeof importLearnCardBundle;
|
|
60
|
-
createLearnCardBundle: typeof createLearnCardBundle;
|
|
61
|
-
readLearnCardBundle: typeof readLearnCardBundle;
|
|
62
|
-
restoreLearnCardFromBundle: ReturnType<typeof createRestoreLearnCardFromBundleHelper>;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const cliGlobals = globalThis as typeof globalThis & CliGlobals;
|
|
66
|
-
|
|
67
|
-
type ReplCompletion = [string[], string];
|
|
68
|
-
|
|
69
|
-
const replGlobals = [
|
|
70
|
-
'learnCard',
|
|
71
|
-
'emptyLearnCard',
|
|
72
|
-
'learnCardFromSeed',
|
|
73
|
-
'initLearnCard',
|
|
74
|
-
'seed',
|
|
75
|
-
'generateRandomSeed',
|
|
76
|
-
'types',
|
|
77
|
-
'getTestCache',
|
|
78
|
-
'copy',
|
|
79
|
-
'getLearnCardBundlePassword',
|
|
80
|
-
'exportLearnCardBundle',
|
|
81
|
-
'importLearnCardBundle',
|
|
82
|
-
'createLearnCardBundle',
|
|
83
|
-
'readLearnCardBundle',
|
|
84
|
-
'restoreLearnCardFromBundle',
|
|
85
|
-
];
|
|
86
|
-
|
|
87
|
-
const replCommands = ['.exit'];
|
|
88
|
-
|
|
89
|
-
const identifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
90
|
-
|
|
91
|
-
const colorizeName = (name: string): string => {
|
|
92
|
-
const colorizedName = gradient(['cyan', 'green'])(name);
|
|
93
|
-
|
|
94
|
-
if (colorizedName !== name || !process.stdout.isTTY) return colorizedName;
|
|
95
|
-
|
|
96
|
-
return `\x1b[36m${name}\x1b[39m`;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const g = {
|
|
100
|
-
learnCard: colorizeName('learnCard'),
|
|
101
|
-
emptyLearnCard: colorizeName('emptyLearnCard'),
|
|
102
|
-
learnCardFromSeed: colorizeName('learnCardFromSeed'),
|
|
103
|
-
initLearnCard: colorizeName('initLearnCard'),
|
|
104
|
-
seed: colorizeName('seed'),
|
|
105
|
-
generateRandomSeed: colorizeName('generateRandomSeed'),
|
|
106
|
-
types: colorizeName('types'),
|
|
107
|
-
getTestCache: colorizeName('getTestCache'),
|
|
108
|
-
getLearnCardBundlePassword: colorizeName('getLearnCardBundlePassword'),
|
|
109
|
-
copy: colorizeName('copy'),
|
|
110
|
-
exportLearnCardBundle: colorizeName('exportLearnCardBundle'),
|
|
111
|
-
importLearnCardBundle: colorizeName('importLearnCardBundle'),
|
|
112
|
-
createLearnCardBundle: colorizeName('createLearnCardBundle'),
|
|
113
|
-
readLearnCardBundle: colorizeName('readLearnCardBundle'),
|
|
114
|
-
restoreLearnCardFromBundle: colorizeName('restoreLearnCardFromBundle'),
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const colorizeReplInput = (input: string): string => {
|
|
118
|
-
return [...replGlobals]
|
|
119
|
-
.sort((a, b) => b.length - a.length)
|
|
120
|
-
.reduce((output, name) => {
|
|
121
|
-
const colorizedName = g[name as keyof typeof g];
|
|
122
|
-
|
|
123
|
-
if (!colorizedName) return output;
|
|
124
|
-
|
|
125
|
-
return output.replace(new RegExp(`\\b${name}\\b`, 'g'), colorizedName);
|
|
126
|
-
}, input);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const copyFunction = (text: string | object | number) => {
|
|
130
|
-
if (typeof text === 'object') {
|
|
131
|
-
text = JSON.stringify(text);
|
|
132
|
-
}
|
|
133
|
-
if (typeof text === 'number') {
|
|
134
|
-
text = text.toString();
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
clipboard.writeSync(text);
|
|
138
|
-
console.log('Copied to clipboard!');
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error(
|
|
141
|
-
'Failed to copy to clipboard:',
|
|
142
|
-
error instanceof Error ? error.message : 'Unknown error'
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const getLearnCardBundlePassword = async (prompt = 'Bundle password: '): Promise<string> => {
|
|
148
|
-
process.stdout.write(prompt);
|
|
149
|
-
|
|
150
|
-
const mutedOutput = new Writable({
|
|
151
|
-
write(_chunk, _encoding, callback) {
|
|
152
|
-
callback();
|
|
153
|
-
},
|
|
154
|
-
});
|
|
155
|
-
const rl = createInterface({ input: process.stdin, output: mutedOutput, terminal: true });
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
return await rl.question('');
|
|
159
|
-
} finally {
|
|
160
|
-
rl.close();
|
|
161
|
-
process.stdout.write('\n');
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const globalAssignmentPattern = /^(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*([\s\S]*?);?\s*$/;
|
|
166
|
-
|
|
167
|
-
const parseGlobalAssignment = (
|
|
168
|
-
source: string
|
|
169
|
-
): { name: string; initializer: string } | undefined => {
|
|
170
|
-
const match = source.match(globalAssignmentPattern);
|
|
171
|
-
|
|
172
|
-
if (!match) return;
|
|
173
|
-
|
|
174
|
-
const name = match[1];
|
|
175
|
-
const initializer = match[2];
|
|
176
|
-
|
|
177
|
-
if (!name || !initializer) return;
|
|
178
|
-
|
|
179
|
-
return { name, initializer };
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const getCurrentToken = (line: string): string => {
|
|
183
|
-
return line.match(/(?:[A-Za-z_$][\w$]*\.)*[A-Za-z_$][\w$]*\.?$/)?.[0] ?? '';
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const getCompletionBase = (token: string): string | undefined => {
|
|
187
|
-
const lastDotIndex = token.lastIndexOf('.');
|
|
188
|
-
|
|
189
|
-
if (lastDotIndex === -1) return;
|
|
190
|
-
|
|
191
|
-
return token.slice(0, lastDotIndex);
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
const getCompletionPrefix = (token: string): string => {
|
|
195
|
-
const lastDotIndex = token.lastIndexOf('.');
|
|
196
|
-
|
|
197
|
-
if (lastDotIndex === -1) return token;
|
|
198
|
-
|
|
199
|
-
return token.slice(lastDotIndex + 1);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const getOwnPropertyNames = (value: unknown): string[] => {
|
|
203
|
-
if (value === null || value === undefined) return [];
|
|
204
|
-
|
|
205
|
-
const names = new Set<string>();
|
|
206
|
-
|
|
207
|
-
let current: unknown = Object(value);
|
|
208
|
-
|
|
209
|
-
while (current && current !== Object.prototype) {
|
|
210
|
-
for (const name of Object.getOwnPropertyNames(current)) names.add(name);
|
|
211
|
-
|
|
212
|
-
current = Object.getPrototypeOf(current);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return [...names];
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const completeBunReplInput = (line: string): ReplCompletion => {
|
|
219
|
-
const token = getCurrentToken(line);
|
|
220
|
-
|
|
221
|
-
if (!token) return [replCommands, ''];
|
|
222
|
-
|
|
223
|
-
const base = getCompletionBase(token);
|
|
224
|
-
const prefix = getCompletionPrefix(token);
|
|
225
|
-
|
|
226
|
-
if (!base) {
|
|
227
|
-
const completions = [...replCommands, ...replGlobals].filter(name =>
|
|
228
|
-
name.startsWith(prefix)
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
return [completions.length ? completions : replGlobals, token];
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
const target = globalThis.eval(base) as unknown;
|
|
236
|
-
const completions = getOwnPropertyNames(target)
|
|
237
|
-
.filter(name => identifierPattern.test(name))
|
|
238
|
-
.filter(name => name.startsWith(prefix))
|
|
239
|
-
.map(name => `${base}.${name}`);
|
|
240
|
-
|
|
241
|
-
return [completions, token];
|
|
242
|
-
} catch {
|
|
243
|
-
return [[], token];
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const evaluateBunReplInput = async (source: string): Promise<unknown> => {
|
|
248
|
-
const trimmedSource = source.trim();
|
|
249
|
-
const expressionSource = trimmedSource.replace(/;+\s*$/, '');
|
|
250
|
-
const statements = trimmedSource
|
|
251
|
-
.split(';')
|
|
252
|
-
.map(statement => statement.trim())
|
|
253
|
-
.filter(Boolean);
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
return await globalThis.eval(`(async () => (${expressionSource}))()`);
|
|
257
|
-
} catch {
|
|
258
|
-
if (statements.length > 1) {
|
|
259
|
-
const lastStatement = statements[statements.length - 1];
|
|
260
|
-
|
|
261
|
-
if (lastStatement && !/^(?:const|let|var)\b/.test(lastStatement)) {
|
|
262
|
-
const body = statements.slice(0, -1).join(';\n');
|
|
263
|
-
const wrappedSource = `${body ? `${body};\n` : ''}return (${lastStatement});`;
|
|
264
|
-
|
|
265
|
-
return await globalThis.eval(`(async () => { ${wrappedSource} })()`);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const globalAssignment = parseGlobalAssignment(trimmedSource);
|
|
270
|
-
|
|
271
|
-
if (globalAssignment) {
|
|
272
|
-
const value = await globalThis.eval(
|
|
273
|
-
`(async () => (${globalAssignment.initializer}))()`
|
|
274
|
-
);
|
|
275
|
-
(globalThis as Record<string, unknown>)[globalAssignment.name] = value;
|
|
276
|
-
return value;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return await globalThis.eval(`(async () => { ${trimmedSource} })()`);
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
const startReadlineRepl = async (colorize?: (input: string) => string): Promise<void> => {
|
|
284
|
-
const rl = createInterface({
|
|
285
|
-
input: process.stdin,
|
|
286
|
-
output: process.stdout,
|
|
287
|
-
prompt: colorize ? colorize('> ') : '> ',
|
|
288
|
-
completer: completeBunReplInput,
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
rl.prompt();
|
|
292
|
-
|
|
293
|
-
for await (const line of rl) {
|
|
294
|
-
const source = line.trim();
|
|
295
|
-
|
|
296
|
-
if (source === '.exit') break;
|
|
297
|
-
|
|
298
|
-
if (!source) {
|
|
299
|
-
rl.prompt();
|
|
300
|
-
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
const result = await evaluateBunReplInput(source);
|
|
306
|
-
|
|
307
|
-
process.stdout.write(
|
|
308
|
-
`${inspect(result, { depth: 6, colors: process.stdout.isTTY })}\n`
|
|
309
|
-
);
|
|
310
|
-
} catch (error) {
|
|
311
|
-
process.stdout.write(
|
|
312
|
-
`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
rl.prompt();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
rl.close();
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
const startBunRepl = async (colorize: (input: string) => string): Promise<void> => {
|
|
323
|
-
await startReadlineRepl(colorize);
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const startCliRepl = async (colorize: (input: string) => string): Promise<void> => {
|
|
327
|
-
if (!('Bun' in globalThis)) {
|
|
328
|
-
const repl = await import('pretty-repl');
|
|
329
|
-
|
|
330
|
-
repl.default.start();
|
|
331
|
-
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
await startBunRepl(colorize);
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
program
|
|
339
|
-
.command('send [recipient]')
|
|
340
|
-
.description(
|
|
341
|
-
'Send a "Quickstart Complete" badge to an email, phone number, profile ID, or DID (prompts if omitted). Creates .env and send.mjs in the current folder.'
|
|
342
|
-
)
|
|
343
|
-
.option('-y, --yes', 'accept defaults without prompting')
|
|
344
|
-
.option('--name <displayName>', 'display name for your issuer profile')
|
|
345
|
-
.option('--badge <name>', 'name of the badge to send (default: "Quickstart Complete")')
|
|
346
|
-
.option('--description <text>', 'badge description')
|
|
347
|
-
.option(
|
|
348
|
-
'--profile-id <id>',
|
|
349
|
-
'public handle for your profile (default: derived from the display name)'
|
|
350
|
-
)
|
|
351
|
-
.option('--network <url>', 'network tRPC URL (default: production)')
|
|
352
|
-
.addOption(
|
|
353
|
-
new Option(
|
|
354
|
-
'--as <profileId>',
|
|
355
|
-
'send as a profile you manage (from `org apply`), signed with its did:web'
|
|
356
|
-
).env('LEARNCARD_AS')
|
|
357
|
-
)
|
|
358
|
-
.option(
|
|
359
|
-
'--template',
|
|
360
|
-
'send using a reusable template and hosted signing authority (default once setup-signing or org apply has run)'
|
|
361
|
-
)
|
|
362
|
-
.option('--no-template', 'sign with the local key even if a signing authority is registered')
|
|
363
|
-
.option('--template-uri <uri>', 'send from a specific template (implies --template)')
|
|
364
|
-
.option('--webhook-url <url>', 'receive ISSUANCE_DELIVERED / ISSUANCE_CLAIMED at this URL')
|
|
365
|
-
.option('--suppress-delivery', 'skip the claim email; you deliver inbox.claimUrl yourself')
|
|
366
|
-
.option(
|
|
367
|
-
'--guardian-email <email>',
|
|
368
|
-
"require a guardian's approval before the recipient can claim"
|
|
369
|
-
)
|
|
370
|
-
.option('--json', 'print a single JSON result on stdout')
|
|
371
|
-
.action(
|
|
372
|
-
async (
|
|
373
|
-
recipient: string | undefined,
|
|
374
|
-
opts: {
|
|
375
|
-
yes?: boolean;
|
|
376
|
-
name?: string;
|
|
377
|
-
badge?: string;
|
|
378
|
-
description?: string;
|
|
379
|
-
profileId?: string;
|
|
380
|
-
network?: string;
|
|
381
|
-
template?: boolean;
|
|
382
|
-
json?: boolean;
|
|
383
|
-
}
|
|
384
|
-
) => {
|
|
385
|
-
out.json = !!opts.json;
|
|
386
|
-
out.result = {};
|
|
387
|
-
const didkit = fs.readFile(
|
|
388
|
-
require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
|
|
389
|
-
);
|
|
390
|
-
try {
|
|
391
|
-
await runSend(recipient, { ...opts, didkit });
|
|
392
|
-
if (out.json) {
|
|
393
|
-
process.stdout.write(
|
|
394
|
-
JSON.stringify({ ok: true, command: 'send', ...out.result }) + '\n'
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
process.exit(0);
|
|
398
|
-
} catch (error) {
|
|
399
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
400
|
-
const firstLine = message.split('\n')[0]!;
|
|
401
|
-
if (out.json) {
|
|
402
|
-
process.stdout.write(
|
|
403
|
-
JSON.stringify({ ok: false, command: 'send', error: firstLine }) + '\n'
|
|
404
|
-
);
|
|
405
|
-
process.exit(1);
|
|
406
|
-
}
|
|
407
|
-
console.error(`\n${firstLine}`);
|
|
408
|
-
// Input mistakes explain themselves; keep the docs link for network/auth failures.
|
|
409
|
-
if (
|
|
410
|
-
!/is not an email address|is a placeholder address|A recipient is required/.test(
|
|
411
|
-
firstLine
|
|
412
|
-
)
|
|
413
|
-
)
|
|
414
|
-
console.error(
|
|
415
|
-
'Troubleshooting: https://docs.learncard.com/start-here/your-first-integration#if-something-goes-wrong'
|
|
416
|
-
);
|
|
417
|
-
process.exit(1);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
const commandOptions = (command: ReturnType<typeof program.command>) =>
|
|
423
|
-
command
|
|
424
|
-
.option('-y, --yes', 'accept defaults without prompting')
|
|
425
|
-
.option('--profile-id <id>', 'public handle for your issuer profile')
|
|
426
|
-
.option('--network <url>', 'network tRPC URL or staging (default: production)')
|
|
427
|
-
.option('--json', 'print a single JSON result on stdout');
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* Shared runner for every subcommand except `send` (which has its own didkit/error
|
|
431
|
-
* formatting). Sets up `out` for this run and, in `--json` mode, prints exactly one
|
|
432
|
-
* envelope on stdout instead of the human-mode stderr message. Pass `wrap: false` for
|
|
433
|
-
* commands (only `verify` today) that already print their own `--json` output.
|
|
434
|
-
*/
|
|
435
|
-
const runCommand = async (
|
|
436
|
-
command: string,
|
|
437
|
-
options: { json?: boolean },
|
|
438
|
-
action: (didkit: Promise<Buffer>) => Promise<void>,
|
|
439
|
-
wrap: boolean = true
|
|
440
|
-
) => {
|
|
441
|
-
out.json = !!options.json;
|
|
442
|
-
out.result = {};
|
|
443
|
-
try {
|
|
444
|
-
await action(
|
|
445
|
-
fs.readFile(require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm'))
|
|
446
|
-
);
|
|
447
|
-
if (out.json && wrap) {
|
|
448
|
-
process.stdout.write(JSON.stringify({ ok: true, command, ...out.result }) + '\n');
|
|
449
|
-
}
|
|
450
|
-
process.exit(process.exitCode || 0);
|
|
451
|
-
} catch (error) {
|
|
452
|
-
const message =
|
|
453
|
-
error instanceof Error
|
|
454
|
-
? error.message.split('\n')[0]!
|
|
455
|
-
: 'Command failed. Please try again.';
|
|
456
|
-
if (out.json && wrap) {
|
|
457
|
-
process.stdout.write(JSON.stringify({ ok: false, command, error: message }) + '\n');
|
|
458
|
-
process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
console.error(message);
|
|
461
|
-
process.exit(1);
|
|
462
|
-
}
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
program
|
|
466
|
-
.command('demo')
|
|
467
|
-
.description('Guided demonstrations using real LearnCard credentials.')
|
|
468
|
-
.command('refresh')
|
|
469
|
-
.description('Send a demo badge, publish an update, and refresh the recipient’s copy.')
|
|
470
|
-
.option('--inbox', 'use Universal Inbox deferred issuance for an address with no account')
|
|
471
|
-
.option('--ui', 'claim and refresh in the local LearnCard app (interactive only)')
|
|
472
|
-
.option('--app-url <url>', 'local LearnCard app URL for --ui (default: http://localhost:3000)')
|
|
473
|
-
.option(
|
|
474
|
-
'--lca-url <url>',
|
|
475
|
-
'local LCA URL for --inbox terminal mode (default: http://localhost:5100/trpc)'
|
|
476
|
-
)
|
|
477
|
-
.option(
|
|
478
|
-
'--email [address]',
|
|
479
|
-
'real-email mode: request a provisional claim email to your own address (requires --inbox --ui; prompts when omitted)'
|
|
480
|
-
)
|
|
481
|
-
.option('-y, --yes', 'run all steps without pausing')
|
|
482
|
-
.option('--network <url>', 'network tRPC URL or staging', 'http://localhost:4000/trpc')
|
|
483
|
-
.option('--json', 'print a single JSON result on stdout (no pauses)')
|
|
484
|
-
.action(options =>
|
|
485
|
-
runCommand('demo refresh', options, async didkit => {
|
|
486
|
-
const { runDemoRefreshCommand } = await import('./demo-refresh-command');
|
|
487
|
-
await runDemoRefreshCommand({ ...options, didkit });
|
|
488
|
-
})
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
commandOptions(
|
|
492
|
-
program.command('consent-contract').description("Connect a user's LearnCard to your platform.")
|
|
493
|
-
)
|
|
494
|
-
.option('--name <name>', 'contract name (default: issuer display name)')
|
|
495
|
-
.option(
|
|
496
|
-
'--redirect-url <url>',
|
|
497
|
-
'callback URL (default: http://localhost:3000/consent-callback)'
|
|
498
|
-
)
|
|
499
|
-
.option('--description <text>', 'what users see when asked to consent')
|
|
500
|
-
.option(
|
|
501
|
-
'--needs-guardian-consent',
|
|
502
|
-
'GameFlow: minors need a guardian to approve the connection'
|
|
503
|
-
)
|
|
504
|
-
.action(options =>
|
|
505
|
-
runCommand('consent-contract', options, async didkit => {
|
|
506
|
-
const { runConsentContract } = await import('./consent-contract');
|
|
507
|
-
await runConsentContract({ ...options, didkit });
|
|
508
|
-
})
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
commandOptions(program.command('embed').description('Put a Claim button on your site.'))
|
|
512
|
-
.option('--name <name>', 'integration name (default: issuer display name)')
|
|
513
|
-
.option(
|
|
514
|
-
'--domains <origins>',
|
|
515
|
-
'comma-separated origins (default: http://localhost:3000,http://localhost:5173)'
|
|
516
|
-
)
|
|
517
|
-
.option('--rotate-key', 'rotate the integration publishable key')
|
|
518
|
-
.action(options =>
|
|
519
|
-
runCommand('embed', options, async didkit => {
|
|
520
|
-
const { runEmbed } = await import('./embed');
|
|
521
|
-
await runEmbed({ ...options, didkit });
|
|
522
|
-
})
|
|
523
|
-
);
|
|
524
|
-
|
|
525
|
-
commandOptions(
|
|
526
|
-
program
|
|
527
|
-
.command('setup-signing')
|
|
528
|
-
.description('Set up LearnCard to sign credentials for your project.')
|
|
529
|
-
)
|
|
530
|
-
.option('--name <name>', 'signing authority name (default: default-issuer)')
|
|
531
|
-
.option('--endpoint <url>', 'register your own VC-API signing service instead (with --did)')
|
|
532
|
-
.option('--did <did>', 'DID of your own signing service (with --endpoint)')
|
|
533
|
-
.action(options =>
|
|
534
|
-
runCommand('setup-signing', options, async didkit => {
|
|
535
|
-
const { runSetupSigning } = await import('./setup-signing');
|
|
536
|
-
await runSetupSigning({ ...options, didkit });
|
|
537
|
-
})
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
commandOptions(
|
|
541
|
-
program.command('token').description('Create a scoped API token and reusable send.sh.')
|
|
542
|
-
)
|
|
543
|
-
.option('--name <name>', 'auth grant name (default: cli-<date>)')
|
|
544
|
-
.option('--scope <scope>', 'space-separated permissions (default: boosts:write)')
|
|
545
|
-
.option('--revoke <grantId>', 'revoke an existing auth grant')
|
|
546
|
-
.option('--expires <days>', 'token lifetime in days (default: no expiry)')
|
|
547
|
-
.option('--list', 'list your auth grants')
|
|
548
|
-
.action(options =>
|
|
549
|
-
runCommand('token', options, async didkit => {
|
|
550
|
-
const { runToken } = await import('./token');
|
|
551
|
-
await runToken({ ...options, didkit });
|
|
552
|
-
})
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
program
|
|
556
|
-
.command('verify <file>')
|
|
557
|
-
.description('Verify a credential or presentation JSON file; use - for stdin.')
|
|
558
|
-
.option('--json', 'print the raw verification result')
|
|
559
|
-
.action((file, options) =>
|
|
560
|
-
runCommand(
|
|
561
|
-
'verify',
|
|
562
|
-
options,
|
|
563
|
-
async didkit => {
|
|
564
|
-
const { runVerify } = await import('./verify');
|
|
565
|
-
await runVerify(file, { ...options, didkit });
|
|
566
|
-
},
|
|
567
|
-
false
|
|
568
|
-
)
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
commandOptions(
|
|
572
|
-
program
|
|
573
|
-
.command('revoke <credentialUri>')
|
|
574
|
-
.description('Revoke or suspend an issued credential on the network.')
|
|
575
|
-
)
|
|
576
|
-
.option('--suspend', 'suspend instead of permanently revoking')
|
|
577
|
-
.option('--template-uri <uri>', 'template URI if it cannot be read from the credential')
|
|
578
|
-
.option(
|
|
579
|
-
'--recipient <profileId>',
|
|
580
|
-
'recipient profile ID if it cannot be read from the credential'
|
|
581
|
-
)
|
|
582
|
-
.action((uri, options) =>
|
|
583
|
-
runCommand('revoke', options, async didkit => {
|
|
584
|
-
const { runRevoke } = await import('./revoke');
|
|
585
|
-
await runRevoke(uri, { ...options, didkit });
|
|
586
|
-
})
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
commandOptions(
|
|
590
|
-
program
|
|
591
|
-
.command('status [activityId]')
|
|
592
|
-
.description('What happened to a credential you sent: created, delivered, claimed.')
|
|
593
|
-
)
|
|
594
|
-
.option('--limit <n>', 'how many recent sends to list (default: 20)')
|
|
595
|
-
.option(
|
|
596
|
-
'--event <type>',
|
|
597
|
-
'only list sends whose latest event is this: created|delivered|claimed|expired|failed'
|
|
598
|
-
)
|
|
599
|
-
.action((activityId, options) =>
|
|
600
|
-
runCommand('status', options, async didkit => {
|
|
601
|
-
const { runStatus } = await import('./status');
|
|
602
|
-
await runStatus(activityId, { ...options, didkit });
|
|
603
|
-
})
|
|
604
|
-
);
|
|
605
|
-
|
|
606
|
-
program
|
|
607
|
-
.command('open [target]')
|
|
608
|
-
.description(
|
|
609
|
-
'Open the LearnCard app signed in as this project. Targets: portal (default), wallet, template, contract, integration.'
|
|
610
|
-
)
|
|
611
|
-
.option('-y, --yes', 'accept defaults without prompting')
|
|
612
|
-
.option('--network <url>', 'network tRPC URL or staging (default: production)')
|
|
613
|
-
.option('--app-url <url>', 'LearnCard app URL for self-hosted or local networks')
|
|
614
|
-
.option('--url-fragment', 'pass the seed in the URL fragment instead of the clipboard')
|
|
615
|
-
.option('--no-browser', 'print the URL without opening a browser')
|
|
616
|
-
.option('--json', 'print a single JSON result on stdout')
|
|
617
|
-
.action((target, options) =>
|
|
618
|
-
runCommand('open', options, async () => {
|
|
619
|
-
const { runOpen, OPEN_TARGETS } = await import('./open');
|
|
620
|
-
if (target && !(target in OPEN_TARGETS))
|
|
621
|
-
throw new Error(
|
|
622
|
-
`Unknown target "${target}". Use one of: ${Object.keys(OPEN_TARGETS).join(', ')}.`
|
|
623
|
-
);
|
|
624
|
-
await runOpen(target, options);
|
|
625
|
-
})
|
|
626
|
-
);
|
|
627
|
-
|
|
628
|
-
commandOptions(
|
|
629
|
-
program.command('webhook [email]').description('Know when your credential was claimed.')
|
|
630
|
-
)
|
|
631
|
-
.option('--to <email>', 'recipient email')
|
|
632
|
-
.option('--url <publicUrl>', 'public HTTPS webhook URL')
|
|
633
|
-
.option('--port <n>', 'receiver port (default: 8787)')
|
|
634
|
-
.option('--name <name>', 'display name for your issuer profile')
|
|
635
|
-
.option(
|
|
636
|
-
'--timeout <seconds>',
|
|
637
|
-
'in --json mode, seconds to wait for webhook events (default: 60)'
|
|
638
|
-
)
|
|
639
|
-
.option('--wait-for-claim', 'in --json mode, also wait for ISSUANCE_CLAIMED before exiting')
|
|
640
|
-
.action((email, options) =>
|
|
641
|
-
runCommand('webhook', options, async didkit => {
|
|
642
|
-
const { runWebhook } = await import('./webhook');
|
|
643
|
-
await runWebhook(email, { ...options, didkit });
|
|
644
|
-
})
|
|
645
|
-
);
|
|
646
|
-
|
|
647
|
-
registerOrgCommand(program, runCommand);
|
|
648
|
-
registerDoctorCommand(program, runCommand);
|
|
649
|
-
registerClrCommand(program, runCommand);
|
|
650
|
-
registerInboxCommand(program, runCommand);
|
|
651
|
-
registerRefreshCommand(program, runCommand);
|
|
652
|
-
registerWhoamiCommand(program, runCommand);
|
|
653
|
-
registerPromoteCommand(program, runCommand);
|
|
654
|
-
|
|
655
|
-
const JOURNEY = [
|
|
656
|
-
'send',
|
|
657
|
-
'status',
|
|
658
|
-
'whoami',
|
|
659
|
-
'setup-signing',
|
|
660
|
-
'token',
|
|
661
|
-
'webhook',
|
|
662
|
-
'consent-contract',
|
|
663
|
-
'embed',
|
|
664
|
-
'verify',
|
|
665
|
-
'revoke',
|
|
666
|
-
'open',
|
|
667
|
-
'org',
|
|
668
|
-
'doctor',
|
|
669
|
-
'promote',
|
|
670
|
-
'clr',
|
|
671
|
-
'inbox',
|
|
672
|
-
'refresh',
|
|
673
|
-
'init',
|
|
674
|
-
'repl',
|
|
675
|
-
];
|
|
676
|
-
|
|
677
|
-
program
|
|
678
|
-
.name('learncard')
|
|
679
|
-
.description(
|
|
680
|
-
'Issue, track, and verify credentials from the terminal. Every command shares the .env in the current folder.'
|
|
681
|
-
)
|
|
682
|
-
.showSuggestionAfterError()
|
|
683
|
-
.configureHelp({
|
|
684
|
-
sortSubcommands: false,
|
|
685
|
-
visibleCommands: cmd =>
|
|
686
|
-
[...cmd.commands].sort((a, b) => JOURNEY.indexOf(a.name()) - JOURNEY.indexOf(b.name())),
|
|
687
|
-
})
|
|
688
|
-
.addHelpText(
|
|
689
|
-
'before',
|
|
690
|
-
'\nStart here: npx @learncard/cli send\nThen: npx @learncard/cli status\n'
|
|
691
|
-
)
|
|
692
|
-
.addHelpText(
|
|
693
|
-
'after',
|
|
694
|
-
'\nEvery command: -y skips prompts, --json prints one machine-readable result, --network staging|<url> picks the network.\n' +
|
|
695
|
-
'Environment:\n' +
|
|
696
|
-
' LC_YES=1 Same as passing -y to every command.\n' +
|
|
697
|
-
' LCA_API_URL, NETWORK_URL Override service URLs for self-hosted networks.\n'
|
|
698
|
-
);
|
|
699
|
-
|
|
700
|
-
const runRepl = async (_seed: string = generateRandomSeed()) => {
|
|
701
|
-
console.clear();
|
|
702
|
-
|
|
703
|
-
const envSeed = process.env.LEARNCARD_CLI_SEED ?? process.env.SEED;
|
|
704
|
-
const seedInput = envSeed ?? _seed;
|
|
705
|
-
const seed = seedInput.padStart(64, '0');
|
|
706
|
-
|
|
707
|
-
console.log(
|
|
708
|
-
gradient(['cyan', 'green'])(figlet.textSync('Learn Card', { font: 'Big Money-ne' }))
|
|
709
|
-
);
|
|
710
|
-
console.log('Welcome to the Learn Card CLI!\n');
|
|
711
|
-
|
|
712
|
-
console.log(`Your seed is ${seed}\n`);
|
|
713
|
-
|
|
714
|
-
if (envSeed) {
|
|
715
|
-
console.log('Using seed from LEARNCARD_CLI_SEED / SEED.\n');
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
console.log('Creating wallet...');
|
|
719
|
-
|
|
720
|
-
cliGlobals.seed = seed;
|
|
721
|
-
cliGlobals.generateRandomSeed = generateRandomSeed;
|
|
722
|
-
cliGlobals.emptyLearnCard = emptyLearnCard;
|
|
723
|
-
cliGlobals.learnCardFromSeed = learnCardFromSeed;
|
|
724
|
-
cliGlobals.initLearnCard = initLearnCard;
|
|
725
|
-
|
|
726
|
-
const didkit = fs.readFile(
|
|
727
|
-
require.resolve('@learncard/didkit-plugin/dist/didkit/didkit_wasm_bg.wasm')
|
|
728
|
-
);
|
|
729
|
-
|
|
730
|
-
const _learnCard = await initLearnCard({
|
|
731
|
-
seed,
|
|
732
|
-
network: true,
|
|
733
|
-
allowRemoteContexts: true,
|
|
734
|
-
didkit,
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
const lcaApiLc = await _learnCard.addPlugin(
|
|
738
|
-
await getLCAPlugin(_learnCard, 'https://api.learncard.app/trpc')
|
|
739
|
-
);
|
|
740
|
-
|
|
741
|
-
cliGlobals.learnCard = await lcaApiLc.addPlugin(getLerRsPlugin(lcaApiLc));
|
|
742
|
-
// Add LinkedClaims plugin so endorse/verify/store/getEndorsements are available in the CLI
|
|
743
|
-
cliGlobals.learnCard = await cliGlobals.learnCard.addPlugin(
|
|
744
|
-
getLinkedClaimsPlugin(cliGlobals.learnCard)
|
|
745
|
-
);
|
|
746
|
-
|
|
747
|
-
// Add OpenBadge v2 wrapper plugin for backwards-compatible OBv2 -> VC wrapping
|
|
748
|
-
cliGlobals.learnCard = await cliGlobals.learnCard.addPlugin(
|
|
749
|
-
openBadgeV2Plugin(cliGlobals.learnCard)
|
|
750
|
-
);
|
|
751
|
-
|
|
752
|
-
// Add Render Method plugin for attaching W3C renderMethod to VCs
|
|
753
|
-
cliGlobals.learnCard = await cliGlobals.learnCard.addPlugin(
|
|
754
|
-
getRenderMethodPlugin(cliGlobals.learnCard)
|
|
755
|
-
);
|
|
756
|
-
|
|
757
|
-
cliGlobals.types = types;
|
|
758
|
-
cliGlobals.getTestCache = getTestCache;
|
|
759
|
-
|
|
760
|
-
cliGlobals.copy = copyFunction;
|
|
761
|
-
cliGlobals.getLearnCardBundlePassword = getLearnCardBundlePassword;
|
|
762
|
-
cliGlobals.exportLearnCardBundle = createExportLearnCardBundleHelper(
|
|
763
|
-
writeLearnCardBundle,
|
|
764
|
-
cliGlobals.learnCard
|
|
765
|
-
);
|
|
766
|
-
|
|
767
|
-
cliGlobals.restoreLearnCardFromBundle = createRestoreLearnCardFromBundleHelper(restoreBundle, {
|
|
768
|
-
network: true,
|
|
769
|
-
allowRemoteContexts: true,
|
|
770
|
-
didkit,
|
|
771
|
-
});
|
|
772
|
-
cliGlobals.importLearnCardBundle = importLearnCardBundle;
|
|
773
|
-
cliGlobals.createLearnCardBundle = createLearnCardBundle;
|
|
774
|
-
cliGlobals.readLearnCardBundle = readLearnCardBundle;
|
|
775
|
-
|
|
776
|
-
// delete 'Creating wallet...' message
|
|
777
|
-
process.stdout.moveCursor?.(0, -1);
|
|
778
|
-
process.stdout.clearLine?.(1);
|
|
779
|
-
|
|
780
|
-
console.log('Wallet created!\n');
|
|
781
|
-
|
|
782
|
-
console.log('┌───────────────────────────────────────────────────────────────┐');
|
|
783
|
-
console.log('│ Variables Available │');
|
|
784
|
-
console.log('├────────────────────────────┬──────────────────────────────────┤');
|
|
785
|
-
console.log('│ Variable │ Description │');
|
|
786
|
-
console.log('├────────────────────────────┼──────────────────────────────────┤');
|
|
787
|
-
console.log(`│ ${g.learnCard} │ Learn Card Wallet │`);
|
|
788
|
-
console.log(`│ ${g.initLearnCard} │ Wallet Instantiation Function │`);
|
|
789
|
-
console.log(`│ ${g.seed} │ Seed used to generate wallet │`);
|
|
790
|
-
console.log(`│ ${g.generateRandomSeed} │ Generates a random seed │`);
|
|
791
|
-
console.log(`│ ${g.types} │ Helpful zod validators │`);
|
|
792
|
-
console.log(`│ ${g.copy} │ Copy text to clipboard │`);
|
|
793
|
-
console.log(`│ ${g.getLearnCardBundlePassword} │ Prompt for bundle password │`);
|
|
794
|
-
console.log(`│ ${g.exportLearnCardBundle} │ Export wallet continuity ZIP │`);
|
|
795
|
-
console.log(`│ ${g.importLearnCardBundle} │ Import continuity ZIP │`);
|
|
796
|
-
console.log(`│ ${g.restoreLearnCardFromBundle} │ Restore original wallet from ZIP │`);
|
|
797
|
-
console.log('└────────────────────────────┴──────────────────────────────────┘');
|
|
798
|
-
|
|
799
|
-
console.log('');
|
|
800
|
-
|
|
801
|
-
console.log('For help/documentation regarding your wallet, please read the documentation at\n');
|
|
802
|
-
|
|
803
|
-
console.log('https://docs.learncard.com/sdks/learncard-core/construction\n');
|
|
804
|
-
|
|
805
|
-
console.log("To get a feel for what's possible, try some of the following commands\n");
|
|
806
|
-
|
|
807
|
-
console.log(
|
|
808
|
-
'┌─────────────────────────┬───────────────────────────────────────────────────────────────────────────────────┐'
|
|
809
|
-
);
|
|
810
|
-
console.log(
|
|
811
|
-
'│ Description │ Command │'
|
|
812
|
-
);
|
|
813
|
-
console.log(
|
|
814
|
-
'├─────────────────────────┼───────────────────────────────────────────────────────────────────────────────────┤'
|
|
815
|
-
);
|
|
816
|
-
console.log(
|
|
817
|
-
`│ View your did │ ${g.learnCard}.id.did(); │`
|
|
818
|
-
);
|
|
819
|
-
console.log(
|
|
820
|
-
`│ Generate an unsigned VC │ ${g.learnCard}.invoke.getTestVc(); │`
|
|
821
|
-
);
|
|
822
|
-
console.log(
|
|
823
|
-
`│ Issue a signed VC │ await ${g.learnCard}.invoke.issueCredential(uvc); │`
|
|
824
|
-
);
|
|
825
|
-
console.log(
|
|
826
|
-
`│ Verify a signed VC │ await ${g.learnCard}.invoke.verifyCredential(vc); │`
|
|
827
|
-
);
|
|
828
|
-
console.log(
|
|
829
|
-
`│ Issue a signed VP │ await ${g.learnCard}.invoke.issuePresentation(vc); │`
|
|
830
|
-
);
|
|
831
|
-
console.log(
|
|
832
|
-
`│ Verify a signed VP │ await ${g.learnCard}.invoke.verifyPresentation(vp); │`
|
|
833
|
-
);
|
|
834
|
-
console.log(
|
|
835
|
-
`│ Prompt bundle password │ const password = await ${g.getLearnCardBundlePassword}(); │`
|
|
836
|
-
);
|
|
837
|
-
console.log(
|
|
838
|
-
`│ Export wallet ZIP │ await ${g.exportLearnCardBundle}(${g.learnCard}, { out: './export.zip', password }); │`
|
|
839
|
-
);
|
|
840
|
-
console.log(
|
|
841
|
-
`│ Restore original wallet │ await ${g.restoreLearnCardFromBundle}('./export.zip', { password }); │`
|
|
842
|
-
);
|
|
843
|
-
console.log(
|
|
844
|
-
'└─────────────────────────┴───────────────────────────────────────────────────────────────────────────────────┘'
|
|
845
|
-
);
|
|
846
|
-
|
|
847
|
-
console.log('');
|
|
848
|
-
|
|
849
|
-
await startCliRepl(colorizeReplInput);
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
commandOptions(
|
|
853
|
-
program
|
|
854
|
-
.command('init')
|
|
855
|
-
.description("Create this folder's issuer identity and profile without sending anything.")
|
|
856
|
-
.option('--name <displayName>', 'display name for your issuer profile')
|
|
857
|
-
).action(options =>
|
|
858
|
-
runCommand('init', options, async didkit => {
|
|
859
|
-
const { runInit } = await import('./init');
|
|
860
|
-
await runInit({ ...options, didkit });
|
|
861
|
-
})
|
|
862
|
-
);
|
|
863
|
-
|
|
864
|
-
program
|
|
865
|
-
.command('repl [seed]')
|
|
866
|
-
.description('Interactive JavaScript console with a LearnCard preloaded (advanced).')
|
|
867
|
-
.action(runRepl);
|
|
868
|
-
|
|
869
|
-
program.version(packageJson.version);
|
|
870
|
-
|
|
871
|
-
// Bare `learncard` with a TTY opens the console, as it always has; anything else is a command.
|
|
872
|
-
if (process.argv.length <= 2 && process.stdin.isTTY) {
|
|
873
|
-
runRepl();
|
|
874
|
-
} else {
|
|
875
|
-
program.parse(process.argv);
|
|
876
|
-
}
|