@learncard/cli 3.6.8 → 4.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +36 -12
  3. package/dist/index.js +6673 -2038
  4. package/package.json +27 -15
  5. package/project.json +0 -42
  6. package/rollup.config.js +0 -34
  7. package/scripts/embed-snippets.ts +0 -47
  8. package/src/auth-grant.test.ts +0 -54
  9. package/src/auth-grant.ts +0 -34
  10. package/src/clr/validate.test.ts +0 -65
  11. package/src/clr/validate.ts +0 -242
  12. package/src/clr.ts +0 -119
  13. package/src/consent-contract.ts +0 -116
  14. package/src/demo-inbox-refresh.test.ts +0 -737
  15. package/src/demo-inbox-refresh.ts +0 -804
  16. package/src/demo-refresh-command.test.ts +0 -57
  17. package/src/demo-refresh-command.ts +0 -22
  18. package/src/demo-refresh-ui.test.ts +0 -66
  19. package/src/demo-refresh-ui.ts +0 -65
  20. package/src/demo-refresh.test.ts +0 -140
  21. package/src/demo-refresh.ts +0 -309
  22. package/src/doctor/checks.test.ts +0 -448
  23. package/src/doctor/checks.ts +0 -497
  24. package/src/doctor.test.ts +0 -67
  25. package/src/doctor.ts +0 -118
  26. package/src/embed.ts +0 -102
  27. package/src/inbox.test.ts +0 -257
  28. package/src/inbox.ts +0 -221
  29. package/src/index.tsx +0 -876
  30. package/src/init.ts +0 -26
  31. package/src/open.test.ts +0 -40
  32. package/src/open.ts +0 -186
  33. package/src/org/apply.test.ts +0 -1108
  34. package/src/org/apply.ts +0 -924
  35. package/src/org/branding.test.ts +0 -60
  36. package/src/org/diff.ts +0 -14
  37. package/src/org/load.ts +0 -50
  38. package/src/org/schema.test.ts +0 -256
  39. package/src/org/schema.ts +0 -216
  40. package/src/org.ts +0 -124
  41. package/src/out.test.ts +0 -40
  42. package/src/out.ts +0 -16
  43. package/src/phase-two.test.ts +0 -154
  44. package/src/project.test.ts +0 -232
  45. package/src/project.ts +0 -489
  46. package/src/promote.test.ts +0 -142
  47. package/src/promote.ts +0 -202
  48. package/src/random.ts +0 -3
  49. package/src/refresh.test.ts +0 -86
  50. package/src/refresh.ts +0 -93
  51. package/src/replHelpers.test.ts +0 -80
  52. package/src/replHelpers.ts +0 -45
  53. package/src/revoke.test.ts +0 -29
  54. package/src/revoke.ts +0 -65
  55. package/src/send-template.test.ts +0 -20
  56. package/src/send.test.ts +0 -321
  57. package/src/send.ts +0 -373
  58. package/src/setup-signing.test.ts +0 -62
  59. package/src/setup-signing.ts +0 -185
  60. package/src/snippet-files.ts +0 -16
  61. package/src/status.test.ts +0 -33
  62. package/src/status.ts +0 -94
  63. package/src/token.test.ts +0 -28
  64. package/src/token.ts +0 -139
  65. package/src/types.d.ts +0 -17
  66. package/src/verify.test.ts +0 -35
  67. package/src/verify.ts +0 -66
  68. package/src/webhook.ts +0 -247
  69. package/src/whoami.test.ts +0 -67
  70. package/src/whoami.ts +0 -129
  71. package/tsconfig.json +0 -38
  72. package/vitest.config.ts +0 -8
package/src/clr.ts DELETED
@@ -1,119 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import type { Command } from 'commander';
3
-
4
- import {
5
- connect,
6
- loadProject,
7
- resolveServices,
8
- PRODUCTION_NETWORK,
9
- type ProjectOptions,
10
- } from './project';
11
- import { out } from './out';
12
- import { validateClr, type ClrValidationProfile } from './clr/validate';
13
-
14
- export type ClrValidateOptions = ProjectOptions & {
15
- profile?: string;
16
- dryRunSign?: boolean;
17
- allowProduction?: boolean;
18
- };
19
-
20
- const PROFILES: readonly ClrValidationProfile[] = ['provisional', 'official'];
21
-
22
- const parseProfile = (value?: string): ClrValidationProfile | undefined => {
23
- if (value === undefined) return undefined;
24
- if ((PROFILES as readonly string[]).includes(value)) return value as ClrValidationProfile;
25
- throw new Error(`Unknown --profile "${value}". Use provisional or official.`);
26
- };
27
-
28
- /**
29
- * The signing key is derived from `credential.issuer`, so a transcript that
30
- * still carries a placeholder issuer would fail to sign for a reason unrelated
31
- * to its content. The dry run signs as this project's wallet instead.
32
- */
33
- export const withIssuer = (
34
- credential: Record<string, unknown>,
35
- did: string
36
- ): Record<string, unknown> => {
37
- const issuer = credential.issuer;
38
- if (issuer && typeof issuer === 'object' && !Array.isArray(issuer))
39
- return { ...credential, issuer: { ...(issuer as Record<string, unknown>), id: did } };
40
- return { ...credential, issuer: did };
41
- };
42
-
43
- export const runClrValidate = async (file: string, options: ClrValidateOptions): Promise<void> => {
44
- const text = await fs.readFile(file, 'utf8');
45
- let json: unknown;
46
- try {
47
- json = JSON.parse(text);
48
- } catch (error) {
49
- const message = error instanceof Error ? error.message : String(error);
50
- throw new Error(`Invalid JSON in ${file}: ${message}`, { cause: error });
51
- }
52
-
53
- const profile = parseProfile(options.profile);
54
- const { errors, warnings, summary } = validateClr(json, { profile });
55
-
56
- for (const error of errors) out.log(`✖ ${error}`);
57
- for (const warning of warnings) out.log(`⚠ ${warning}`);
58
-
59
- const ok = errors.length === 0;
60
- if (!ok) process.exitCode = 1;
61
- let signOk = true;
62
-
63
- if (options.dryRunSign) {
64
- if (!ok) {
65
- out.log('Skipping --dry-run-sign: fix validation errors first.');
66
- } else {
67
- const project = await loadProject(process.cwd());
68
- const services = resolveServices(project.env, options.network);
69
- if (services.network === PRODUCTION_NETWORK && !options.allowProduction) {
70
- throw new Error(
71
- 'Refusing --dry-run-sign against production. Pass --network staging or --allow-production.'
72
- );
73
- }
74
- const learnCard = await connect(project, options);
75
- const signed = await learnCard.invoke.issueCredential(
76
- withIssuer(json as Record<string, unknown>, learnCard.id.did()) as never
77
- );
78
- const verification = await learnCard.invoke.verifyCredential(signed);
79
- out.log('Signed: true (dry run only — nothing was sent or stored)');
80
- for (const check of verification.checks) out.log(`✓ ${check}`);
81
- for (const warning of verification.warnings) out.log(`! ${warning}`);
82
- for (const error of verification.errors) out.log(`✗ ${error}`);
83
- if (verification.errors.length) {
84
- signOk = false;
85
- process.exitCode = 1;
86
- }
87
- out.set({ signed: true, verification });
88
- }
89
- }
90
-
91
- out.set({ ok: ok && signOk, errors, warnings, summary });
92
- };
93
-
94
- export const registerClrCommand = (
95
- program: Command,
96
- run: (
97
- command: string,
98
- options: { json?: boolean },
99
- action: (didkit: Promise<Buffer>) => Promise<void>,
100
- wrap?: boolean
101
- ) => Promise<void>
102
- ): void => {
103
- const clr = program.command('clr').description('CLR 2.0 transcript tools.');
104
- clr.command('validate <file>')
105
- .description('Validate a CLR 2.0 transcript file against schema and issuer-profile rules.')
106
- .option('--profile <name>', 'provisional or official profile checks')
107
- .option(
108
- '--dry-run-sign',
109
- 'sign + verify in memory to catch signing failures; never sends or stores'
110
- )
111
- .option('--allow-production', 'allow --dry-run-sign against the production network')
112
- .option('--network <url>', 'network tRPC URL or staging (default: production)')
113
- .option('--json', 'print a single JSON result on stdout')
114
- .action((file, options) =>
115
- run('clr validate', options, async didkit => {
116
- await runClrValidate(file, { ...options, didkit });
117
- })
118
- );
119
- };
@@ -1,116 +0,0 @@
1
- import {
2
- connect,
3
- createPrompts,
4
- ensureIdentity,
5
- ensureProfile,
6
- KEYS,
7
- loadProject,
8
- localizeSnippet,
9
- resolveServices,
10
- saveProject,
11
- STAGING_NETWORK,
12
- PRODUCTION_NETWORK,
13
- type ProjectOptions,
14
- appUrlFor,
15
- } from './project';
16
- import {
17
- CREATE_CONTRACT_MJS,
18
- CONSENT_CALLBACK_MJS,
19
- READ_USER_DATA_MJS,
20
- ISSUE_THROUGH_CONTRACT_MJS,
21
- } from './generated/snippets';
22
- import { writeSnippet } from './snippet-files';
23
- import { out } from './out';
24
-
25
- export const consentUrl = (uri: string, returnTo: string, network: string): string => {
26
- const url = new URL('/consent-flow', appUrlFor(network));
27
- url.searchParams.set('uri', uri);
28
- url.searchParams.set('returnTo', returnTo);
29
- return url.toString();
30
- };
31
-
32
- export const runConsentContract = async (
33
- options: ProjectOptions & {
34
- redirectUrl?: string;
35
- description?: string;
36
- needsGuardianConsent?: boolean;
37
- }
38
- ): Promise<void> => {
39
- const project = await loadProject(process.cwd());
40
- const returnTo =
41
- options.redirectUrl || project.env.RETURN_TO || 'http://localhost:3000/consent-callback';
42
- if (!['http:', 'https:'].includes(new URL(returnTo).protocol)) {
43
- throw new Error('Redirect URL must use HTTP or HTTPS.');
44
- }
45
- const identity = await ensureIdentity(project, options);
46
- const learnCard = await connect(project, options);
47
- await ensureProfile(learnCard, identity, project);
48
- let uri = project.env[KEYS.CONTRACT_URI];
49
- if (!uri) {
50
- const prompts = createPrompts(options.yes);
51
- let name: string;
52
- try {
53
- name = options.name ?? (await prompts.ask('Contract name', identity.displayName));
54
- } finally {
55
- prompts.close();
56
- }
57
- const contract = {
58
- name,
59
- description:
60
- options.description ??
61
- 'Allow us to read your name and achievements and send achievements.',
62
- ...(options.needsGuardianConsent ? { needsGuardianConsent: true } : {}),
63
- redirectUrl: returnTo,
64
- contract: {
65
- read: {
66
- personal: { name: { required: false } },
67
- credentials: { categories: { Achievement: { required: false } } },
68
- },
69
- write: {
70
- personal: {},
71
- credentials: { categories: { Achievement: { required: false } } },
72
- },
73
- },
74
- };
75
- uri = await learnCard.invoke.createContract(contract);
76
- out.log('Created your consent contract.');
77
- } else {
78
- out.log('Reusing your saved consent contract.');
79
- }
80
- await saveProject(project, { [KEYS.CONTRACT_URI]: uri, RETURN_TO: returnTo });
81
- const services = resolveServices(project.env, options.network);
82
- const files: string[] = [];
83
- for (const [file, source] of Object.entries({
84
- 'create-contract.mjs': CREATE_CONTRACT_MJS,
85
- 'consent-callback.mjs': CONSENT_CALLBACK_MJS,
86
- 'read-user-data.mjs': READ_USER_DATA_MJS,
87
- 'issue-through-contract.mjs': ISSUE_THROUGH_CONTRACT_MJS,
88
- })) {
89
- // The canonical tutorial uses a destructured SECURE_SEED; normalize only for localization.
90
- const localized =
91
- services.network === PRODUCTION_NETWORK
92
- ? source
93
- : localizeSnippet(
94
- source.replaceAll(
95
- 'seed: SECURE_SEED, network: true',
96
- 'seed: process.env.SECURE_SEED, network: true'
97
- ),
98
- services
99
- );
100
- if (await writeSnippet(file, localized)) files.push(`./${file}`);
101
- }
102
- const contractConsentUrl = consentUrl(uri, returnTo, services.network);
103
- out.log(contractConsentUrl);
104
- if (![PRODUCTION_NETWORK, STAGING_NETWORK].includes(services.network)) {
105
- out.log(
106
- 'Note: this is a production app URL; use an app connected to your local network to consent.'
107
- );
108
- }
109
- out.log(
110
- 'The tutorial create-contract.mjs creates a new contract and requires HTTPS; reuse CONTRACT_URI for this dev setup.'
111
- );
112
- out.log('Read scripts require a verified CONSENT_VP, not a bare DID.');
113
- out.log('Next: node --env-file=.env consent-callback.mjs');
114
- out.log('See the contract in the app: npx @learncard/cli open contract');
115
- out.set({ contractUri: uri, consentUrl: contractConsentUrl, returnTo, files });
116
- };