@learncard/partner-connect 0.2.14 → 0.3.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 CHANGED
@@ -305,6 +305,102 @@ if (recipients.hasMore) {
305
305
 
306
306
  **Returns:** `{ records: TemplateRecipientRecord[], hasMore: boolean, cursor?: string, total?: number }`
307
307
 
308
+ ### `sendNotification(input)`
309
+
310
+ Send a notification to the current user from this app. The notification appears in the user's LearnCard notification inbox, even after they leave the app.
311
+
312
+ ```typescript
313
+ await learnCard.sendNotification({
314
+ title: 'Sprint Bonus!',
315
+ body: '+10 coins from Sprint 42',
316
+ actionPath: '/',
317
+ category: 'reward',
318
+ });
319
+ ```
320
+
321
+ **Parameters:**
322
+
323
+ - `title` _(optional)_: Notification title
324
+ - `body` _(optional)_: Notification body text
325
+ - `actionPath` _(optional)_: Deep link path within the app (e.g. `'/prizes'`). Must be an absolute pathname starting with `/`. This path is appended to the app's configured embed URL when the user taps the notification. For example, if your embed URL is `https://myapp.com` and `actionPath` is `'/challenges/42'`, the app will open at `https://myapp.com/challenges/42`. Hash routes (e.g. `'/#/page'`) are **not** supported — use pathname-based routing.
326
+ - `category` _(optional)_: Grouping category (e.g. `'reward'`, `'announcement'`, `'status'`)
327
+ - `priority` _(optional)_: `'normal'` (default) or `'high'`. Affects visual styling of the notification card and toast. Does not change delivery priority or ordering.
328
+
329
+ At least one of `title` or `body` is required.
330
+
331
+ **Returns:** `{ sent: boolean }`
332
+
333
+ > **Note:** This method sends a notification to the _current_ user (self-notification) via the `send-notification` app event. For server-to-server notifications to arbitrary users, use the `POST /app-store/listing/{listingId}/notify` brain-service route directly from your app backend.
334
+
335
+ ---
336
+
337
+ ### `incrementCounter(key, amount)`
338
+
339
+ Increment or decrement an app-scoped counter for the current user. Counters are scoped to (user, app, key). If the counter does not exist, it is created with the given amount as its initial value.
340
+
341
+ ```typescript
342
+ // Add 10 coins
343
+ const result = await learnCard.incrementCounter('coins', 10);
344
+ console.log(result.newValue); // 10
345
+
346
+ // Spend 5 coins
347
+ const spent = await learnCard.incrementCounter('coins', -5);
348
+ console.log(spent.newValue); // 5
349
+ ```
350
+
351
+ **Parameters:**
352
+
353
+ - `key` _(required)_: Counter name. Must match `[a-zA-Z0-9_-]+`, max 64 characters.
354
+ - `amount` _(required)_: Integer value to add. Use a negative integer to decrement.
355
+
356
+ **Returns:** `{ key: string, previousValue: number, newValue: number }`
357
+
358
+ **Limits:**
359
+
360
+ - Max 50 distinct counter keys per user per app
361
+ - Max 100 writes per user per app per minute
362
+ - Amount must be a finite integer
363
+
364
+ ---
365
+
366
+ ### `getCounter(key)`
367
+
368
+ Read the current value of an app-scoped counter. Returns `{ value: 0 }` if the counter does not exist.
369
+
370
+ ```typescript
371
+ const { value } = await learnCard.getCounter('coins');
372
+ console.log('Balance:', value);
373
+ ```
374
+
375
+ **Parameters:**
376
+
377
+ - `key` _(required)_: Counter name (same format as `incrementCounter`)
378
+
379
+ **Returns:** `{ key: string, value: number, updatedAt: string | null }`
380
+
381
+ ---
382
+
383
+ ### `getCounters(keys?)`
384
+
385
+ Read multiple app-scoped counters at once. If `keys` is omitted, returns all counters for this app.
386
+
387
+ ```typescript
388
+ // Specific keys
389
+ const { counters } = await learnCard.getCounters(['coins', 'spins', 'streak']);
390
+ counters.forEach(c => console.log(c.key, c.value));
391
+
392
+ // All counters
393
+ const all = await learnCard.getCounters();
394
+ ```
395
+
396
+ **Parameters:**
397
+
398
+ - `keys` _(optional)_: Array of counter names to fetch (max 50). Omit to return all.
399
+
400
+ **Returns:** `{ counters: Array<{ key: string, value: number, updatedAt: string | null }> }`
401
+
402
+ ---
403
+
308
404
  ### `launchFeature(featurePath, initialPrompt?)`
309
405
 
310
406
  Launch a feature in the LearnCard host application.
package/dist/index.d.ts CHANGED
@@ -10,44 +10,73 @@ export { AppEvent, AppEventResponse, CheckCredentialEvent, SendCredentialEvent }
10
10
  */
11
11
  interface PartnerConnectOptions {
12
12
  /**
13
- * The origin(s) of the LearnCard host
14
- *
15
- * This can be a single string or an array of strings to serve as a whitelist for
16
- * the `lc_host_override` query parameter.
17
- *
18
- * **Origin Configuration Hierarchy:**
19
- * 1. **Hardcoded Default**: `https://learncard.app` (security anchor)
20
- * 2. **Query Parameter Override**: `?lc_host_override=https://staging.learncard.app`
21
- * - Checked against whitelist if `hostOrigin` is provided
22
- * - Used for staging/testing environments
23
- * 3. **Configured Origin**: First value in array or single string value
24
- *
25
- * **Security Model:**
26
- * - The SDK enforces STRICT origin validation
27
- * - Incoming messages must EXACTLY match the active host origin
28
- * - Prevents origin spoofing: even if malicious query param is added,
29
- * messages from unauthorized origins are rejected
13
+ * The origin(s) of the LearnCard host.
14
+ *
15
+ * Each entry may be either an **exact origin** (`https://learncard.app`) or a
16
+ * **wildcard pattern** where `*` stands in for a single DNS label portion in
17
+ * the host portion of the origin (e.g. `https://*.learncard.app`,
18
+ * `https://*.vetpass.app`). Wildcards are **only** allowed in the host and
19
+ * only as label(s); the protocol and port must always match exactly.
20
+ *
21
+ * Wildcard patterns match any non-empty chain of labels. So
22
+ * `https://*.learncard.app` matches both `https://staging.learncard.app` and
23
+ * `https://pr-123.preview.learncard.app`, but **not** `https://learncard.app`
24
+ * itself (include the bare origin explicitly if you need it) and **not**
25
+ * `https://evil.learncard.app.attacker.com` (the suffix must match).
26
+ *
27
+ * **Origin Configuration Hierarchy at runtime:**
28
+ * 1. `window.location.ancestorOrigins[0]` (when available) — the real parent
29
+ * origin as reported by the browser, validated against the effective
30
+ * whitelist. This source cannot be spoofed by a malicious query param.
31
+ * 2. `?lc_host_override=<origin>` query parameter — validated against the
32
+ * whitelist. Used by the LearnCard host to tell the SDK which origin it
33
+ * is loading from.
34
+ * 3. `sessionStorage['lc_host_override']` — a previously-validated override
35
+ * persisted across in-iframe navigation.
36
+ * 4. First value in the configured `hostOrigin` array / single string.
37
+ * 5. `PartnerConnect.DEFAULT_HOST_ORIGIN` (`https://learncard.app`).
38
+ *
39
+ * The partner app's configured whitelist is combined with a small built-in
40
+ * list of LearnCard tenant domains (see `disableDefaultTenants` to opt out).
41
+ * This lets a partner app work out-of-the-box inside any current or future
42
+ * `*.learncard.app`, `*.learncard.ai`, or `*.vetpass.app` tenant without a
43
+ * re-deploy.
30
44
  *
31
45
  * **Examples:**
32
46
  *
33
- * Single origin (production):
47
+ * Single origin (production only):
34
48
  * ```typescript
35
49
  * hostOrigin: 'https://learncard.app'
36
- * // Uses: https://learncard.app
37
- * // Override: ?lc_host_override=https://staging.learncard.app (not validated)
38
50
  * ```
39
51
  *
40
- * Multiple origins (whitelist for staging):
52
+ * Wildcard whitelist (covers staging + preview):
53
+ * ```typescript
54
+ * hostOrigin: ['https://learncard.app', 'https://*.learncard.app']
55
+ * ```
56
+ *
57
+ * Custom tenant alongside the built-in LearnCard defaults:
41
58
  * ```typescript
42
- * hostOrigin: ['https://learncard.app', 'https://staging.learncard.app']
43
- * // Default: https://learncard.app
44
- * // Override: ?lc_host_override=https://staging.learncard.app (validated)
45
- * // Invalid: ?lc_host_override=https://evil.com (rejected)
59
+ * hostOrigin: ['https://partner.example.com']
60
+ * // learncard.app / *.learncard.app / *.learncard.ai / vetpass.app / *.vetpass.app
61
+ * // are ALSO trusted because disableDefaultTenants is false.
46
62
  * ```
47
63
  *
48
64
  * @default 'https://learncard.app'
49
65
  */
50
66
  hostOrigin?: string | string[];
67
+ /**
68
+ * Opt out of the built-in LearnCard tenant whitelist.
69
+ *
70
+ * By default, the SDK merges `hostOrigin` with a curated list of LearnCard
71
+ * and tenant domains (see `PartnerConnect.DEFAULT_TRUSTED_TENANTS`) so that
72
+ * partner apps work on any LearnCard-managed tenant without reconfiguration.
73
+ *
74
+ * Set to `true` if you want the partner app to **only** trust the origins
75
+ * you pass in `hostOrigin`.
76
+ *
77
+ * @default false
78
+ */
79
+ disableDefaultTenants?: boolean;
51
80
  /**
52
81
  * Whether to allow native app origins (default: true)
53
82
  *
@@ -158,6 +187,17 @@ interface RequestConsentOptions {
158
187
  */
159
188
  redirect?: boolean;
160
189
  }
190
+ /**
191
+ * Payload for REQUEST_CONSENT action
192
+ */
193
+ interface RequestConsentPayload {
194
+ /**
195
+ * URI of the consent contract. If not provided, the system will attempt
196
+ * to use the contract configured for the current app listing.
197
+ */
198
+ contractUri?: string;
199
+ redirect?: boolean;
200
+ }
161
201
  /**
162
202
  * Response from REQUEST_CONSENT action
163
203
  */
@@ -242,17 +282,236 @@ interface TemplateRecipientsResponse {
242
282
  cursor?: string;
243
283
  total?: number;
244
284
  }
285
+ /**
286
+ * Options for REQUEST_LEARNER_CONTEXT action
287
+ */
288
+ interface RequestLearnerContextOptions {
289
+ /**
290
+ * Whether to include credentials in the context
291
+ * @default true
292
+ */
293
+ includeCredentials?: boolean;
294
+ /**
295
+ * Whether to include personal data (name, bio, etc.) in the context
296
+ * @default false
297
+ */
298
+ includePersonalData?: boolean;
299
+ /**
300
+ * Format of the response
301
+ * - 'prompt': Returns LLM-ready formatted text
302
+ * - 'structured': Returns structured data object
303
+ * @default 'prompt'
304
+ */
305
+ format?: 'prompt' | 'structured';
306
+ /**
307
+ * Optional instructions to guide the LLM prompt generation
308
+ */
309
+ instructions?: string;
310
+ /**
311
+ * Level of detail in the generated prompt
312
+ * @default 'compact'
313
+ */
314
+ detailLevel?: 'compact' | 'expanded';
315
+ }
316
+ /**
317
+ * Raw data included in learner context response (when format is 'structured')
318
+ */
319
+ interface LearnerContextRawData {
320
+ /** Array of Verifiable Credentials */
321
+ credentials: unknown[];
322
+ /** Personal data if requested and available (name, bio, etc.) */
323
+ personalData?: Record<string, unknown>;
324
+ }
325
+ /**
326
+ * Response from REQUEST_LEARNER_CONTEXT action
327
+ */
328
+ interface LearnerContextResponse {
329
+ /** LLM-ready formatted prompt text */
330
+ prompt: string;
331
+ /** Raw structured data (only included when format is 'structured') */
332
+ raw?: LearnerContextRawData;
333
+ /** User's DID */
334
+ did: string;
335
+ /** User's display name if available */
336
+ displayName?: string;
337
+ }
338
+ /**
339
+ * Keywords for next steps in summary credential data
340
+ */
341
+ interface SummaryCredentialKeyword {
342
+ occupations: string[] | null;
343
+ careers: string[] | null;
344
+ jobs: string[] | null;
345
+ skills: string[] | null;
346
+ fieldOfStudy: string | null;
347
+ }
348
+ /**
349
+ * Skill item in summary credential data
350
+ */
351
+ interface SummaryCredentialSkill {
352
+ title: string;
353
+ description: string;
354
+ }
355
+ /**
356
+ * Next step item in summary credential data
357
+ *
358
+ * `keywords` is optional. Omit it entirely (or pass `undefined`) when you do not
359
+ * have taxonomy data — you no longer need to pass a struct of `null` fields.
360
+ */
361
+ interface SummaryCredentialNextStep {
362
+ title: string;
363
+ description: string;
364
+ keywords?: SummaryCredentialKeyword;
365
+ }
366
+ /**
367
+ * Reflection item in summary credential data
368
+ */
369
+ interface SummaryCredentialReflection {
370
+ title: string;
371
+ description: string;
372
+ }
373
+ /**
374
+ * Summary data for an AI Session credential
375
+ * Contains structured information about what was learned
376
+ */
377
+ interface SummaryCredentialData {
378
+ /** Short, concise title for the learning session or credential */
379
+ title: string;
380
+ /** Comprehensive summary of what happened during the session */
381
+ summary: string;
382
+ /** Bullet points of key knowledge gained */
383
+ learned: string[];
384
+ /** Categorized skills learned during the session */
385
+ skills: SummaryCredentialSkill[];
386
+ /** Recommended follow-up activities or learning modules */
387
+ nextSteps: SummaryCredentialNextStep[];
388
+ /** Reflections on the learning experience */
389
+ reflections: SummaryCredentialReflection[];
390
+ }
391
+ /**
392
+ * Input for creating and sending an AI Session credential
393
+ */
394
+ interface SendAiSessionCredentialInput {
395
+ /** Title of this specific AI session */
396
+ sessionTitle: string;
397
+ /** Structured summary data about what was learned */
398
+ summaryData: SummaryCredentialData;
399
+ /** Optional metadata for the session */
400
+ metadata?: Record<string, unknown>;
401
+ }
402
+ /**
403
+ * Response from sending an AI Session credential
404
+ */
405
+ interface SendAiSessionCredentialResponse {
406
+ /** URI of the AI Topic (parent) boost */
407
+ topicUri: string;
408
+ /** URI of the topic credential, if a new topic was created */
409
+ topicCredentialUri?: string;
410
+ /** URI of the created AI Session credential */
411
+ sessionCredentialUri: string;
412
+ /** URI of the session boost (child of topic) */
413
+ sessionBoostUri: string;
414
+ /** Whether a new topic was created (true) or existing was used (false) */
415
+ isNewTopic: boolean;
416
+ }
417
+ /**
418
+ * Input for sending a notification to the current user from this app.
419
+ * The notification appears in the user's LearnCard notification inbox.
420
+ */
421
+ interface AppNotificationInput {
422
+ /** Notification title */
423
+ title?: string;
424
+ /** Notification body text */
425
+ body?: string;
426
+ /** Deep link path within the app (e.g. '/prizes') */
427
+ actionPath?: string;
428
+ /** Grouping category (e.g. 'reward', 'announcement', 'status') */
429
+ category?: string;
430
+ /** Notification priority */
431
+ priority?: 'normal' | 'high';
432
+ }
433
+ /**
434
+ * Response from sendNotification
435
+ */
436
+ interface AppNotificationResponse {
437
+ sent: boolean;
438
+ }
439
+ /**
440
+ * Response from incrementCounter
441
+ */
442
+ interface IncrementCounterResponse {
443
+ key: string;
444
+ previousValue: number;
445
+ newValue: number;
446
+ }
447
+ /**
448
+ * Response from getCounter
449
+ */
450
+ interface GetCounterResponse {
451
+ key: string;
452
+ value: number;
453
+ updatedAt: string | null;
454
+ }
455
+ /**
456
+ * Response from getCounters
457
+ */
458
+ interface GetCountersResponse {
459
+ counters: GetCounterResponse[];
460
+ }
245
461
  /**
246
462
  * Error codes that can be returned by the LearnCard host
247
463
  */
248
464
  type ErrorCode = 'LC_TIMEOUT' | 'LC_UNAUTHENTICATED' | 'CREDENTIAL_NOT_FOUND' | 'USER_REJECTED' | 'UNAUTHORIZED' | 'TEMPLATE_NOT_FOUND' | 'BOOST_NOT_FOUND' | 'INSUFFICIENT_PERMISSIONS' | string;
249
465
  /**
250
- * Error object returned when a request fails
466
+ * Error object returned when a request fails.
467
+ *
468
+ * Historically the SDK rejected with a plain `{ code, message }` object. As of
469
+ * v0.3.0 we reject with a {@link PartnerConnectError} instance instead, which
470
+ * still satisfies this interface (it has both `code` and `message` fields), so
471
+ * existing consumers that do `if (err.code === '...')` continue to work
472
+ * unchanged.
251
473
  */
252
474
  interface LearnCardError {
253
475
  code: ErrorCode;
254
476
  message: string;
255
477
  }
478
+ /**
479
+ * Typed error class for all Partner Connect SDK rejections.
480
+ *
481
+ * Use `instanceof PartnerConnectError` to narrow caught errors and unlock
482
+ * exhaustive `switch` checks on `code`. Both `code` and `message` are present
483
+ * (so the legacy `LearnCardError` object shape is preserved), and `name` is
484
+ * always `'PartnerConnectError'`.
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * try {
489
+ * await learnCard.requestLearnerContext();
490
+ * } catch (err) {
491
+ * if (err instanceof PartnerConnectError) {
492
+ * switch (err.code) {
493
+ * case 'LC_UNAUTHENTICATED': showLogin(); break;
494
+ * case 'USER_REJECTED': showPrivacyNotice(); break;
495
+ * case 'UNAUTHORIZED': showPermissionsError(); break;
496
+ * default: console.error(err);
497
+ * }
498
+ * }
499
+ * }
500
+ * ```
501
+ */
502
+ declare class PartnerConnectError extends Error implements LearnCardError {
503
+ readonly code: ErrorCode;
504
+ constructor(code: ErrorCode, message: string);
505
+ /**
506
+ * Wrap any incoming `LearnCardError`-shaped value into a `PartnerConnectError`.
507
+ * Returns the value unchanged if it is already an instance.
508
+ *
509
+ * Used internally at every reject site so callers always receive a typed
510
+ * `PartnerConnectError`, regardless of whether the failure originated from
511
+ * the host (over postMessage), an SDK timeout, or `destroy()`.
512
+ */
513
+ static from(input: LearnCardError | unknown): PartnerConnectError;
514
+ }
256
515
  /**
257
516
  * Internal message structure sent via postMessage
258
517
  */
@@ -307,6 +566,19 @@ interface PendingRequest {
307
566
  declare class PartnerConnect {
308
567
  /** Default host origin (security anchor) */
309
568
  static readonly DEFAULT_HOST_ORIGIN = "https://learncard.app";
569
+ /**
570
+ * Built-in list of LearnCard-managed tenant origins.
571
+ *
572
+ * These are merged with the partner app's configured `hostOrigin` whitelist
573
+ * unless `disableDefaultTenants: true` is passed. This lets a partner app
574
+ * run inside any current or future LearnCard tenant (staging, preview,
575
+ * VetPass, etc.) without needing a re-deploy each time a new tenant is
576
+ * onboarded.
577
+ *
578
+ * Patterns follow the same rules as user-supplied `hostOrigin` entries:
579
+ * `*` is a wildcard for one or more DNS labels in the host portion.
580
+ */
581
+ static readonly DEFAULT_TRUSTED_TENANTS: readonly string[];
310
582
  private hostOrigins;
311
583
  private activeHostOrigin;
312
584
  private allowNativeAppOrigins;
@@ -318,16 +590,60 @@ declare class PartnerConnect {
318
590
  constructor(options?: PartnerConnectOptions);
319
591
  /**
320
592
  * Configure the active host origin using the following hierarchy:
321
- * 1. Check for `lc_host_override` query parameter (for staging/testing)
322
- * 2. Fall back to first configured origin
323
- * 3. Fall back to DEFAULT_HOST_ORIGIN
324
- *
325
- * This origin will be used for all outgoing messages and incoming message validation.
593
+ * 1. `window.location.ancestorOrigins[0]` (when supported) — the browser's
594
+ * view of who our parent frame is. Cannot be forged by a malicious
595
+ * `lc_host_override` query param and therefore takes precedence.
596
+ * 2. `?lc_host_override=<origin>` query param (for staging / cross-tenant).
597
+ * 3. `sessionStorage` value saved from a previously-validated override.
598
+ * 4. First configured origin.
599
+ * 5. `DEFAULT_HOST_ORIGIN`.
600
+ *
601
+ * When a valid override is found in the query parameter, it is persisted
602
+ * to sessionStorage so subsequent in-iframe navigations in the same tab
603
+ * continue to use the same active origin.
604
+ */
605
+ private static readonly SESSION_STORAGE_KEY;
606
+ /**
607
+ * Read `window.location.ancestorOrigins[0]` without throwing if the
608
+ * property is unavailable (Firefox) or the list is empty (top-level
609
+ * context, e.g. running outside of an iframe).
326
610
  */
611
+ private readAncestorOrigin;
327
612
  private configureActiveOrigin;
613
+ private persistOverride;
328
614
  private isOriginNativeApp;
329
615
  /**
330
- * Check if an origin is in the configured whitelist
616
+ * Internal placeholder substituted in for `*` so that `new URL(...)` can
617
+ * parse a wildcard pattern. Chosen to be a syntactically-valid DNS label
618
+ * that cannot collide with a real hostname.
619
+ */
620
+ private static readonly WILDCARD_PLACEHOLDER;
621
+ /** `*` (any number of occurrences) for replacement in the pattern. */
622
+ private static readonly WILDCARD_REGEX;
623
+ /** The required leading-label form a wildcard pattern must take. */
624
+ private static readonly WILDCARD_LEADING_PREFIX;
625
+ /**
626
+ * Check whether a candidate origin matches a configured whitelist entry.
627
+ *
628
+ * Supports exact matches and wildcard patterns. A wildcard entry has the
629
+ * form `<protocol>://*.<domain>` and matches any origin with the same
630
+ * protocol, same port, and a host ending in `.<domain>` with at least
631
+ * one non-empty DNS label in place of the `*`.
632
+ *
633
+ * Examples with pattern `https://*.learncard.app`:
634
+ * - `https://staging.learncard.app` → match
635
+ * - `https://pr-1.preview.learncard.app` → match
636
+ * - `https://learncard.app` → no match (no subdomain)
637
+ * - `http://staging.learncard.app` → no match (protocol mismatch)
638
+ * - `https://learncard.app.attacker.com` → no match (suffix mismatch)
639
+ *
640
+ * Exposed as a public static so it can be unit-tested directly without
641
+ * standing up a full SDK instance.
642
+ */
643
+ static matchesOriginPattern(candidate: string, pattern: string): boolean;
644
+ /**
645
+ * Check if an origin is in the effective whitelist (exact origins +
646
+ * wildcard patterns + optional native-app origins).
331
647
  */
332
648
  private isOriginInWhitelist;
333
649
  /**
@@ -513,25 +829,30 @@ declare class PartnerConnect {
513
829
  /**
514
830
  * Request user consent for permissions
515
831
  *
516
- * @param contractUri - URI of the consent contract
832
+ * @param contractUri - URI of the consent contract (optional for App Store apps with configured contracts)
833
+ * @param options - Additional options including redirect behavior
517
834
  * @returns Promise resolving to consent response
518
835
  *
519
836
  * @example
520
837
  * ```typescript
521
- * // Without redirect (default) - returns VP in response if app owns the contract
838
+ * // With explicit contract URI (for external/non-app store integrations)
522
839
  * const response = await learnCard.requestConsent('lc:network:network.learncard.com/trpc:contract:abc123');
523
840
  * if (response.granted) {
524
841
  * console.log('User granted consent');
525
- * if (response.vp) {
526
- * console.log('VP:', response.vp);
527
- * }
842
+ * }
843
+ *
844
+ * // Without contract URI (uses app's configured contract from integration)
845
+ * // This works for App Store apps that have configured a contract in their integration
846
+ * const response = await learnCard.requestConsent();
847
+ * if (response.granted) {
848
+ * console.log('User granted consent using listing contract');
528
849
  * }
529
850
  *
530
851
  * // With redirect - redirects to contract's redirectUrl with VP in URL params
531
- * const response = await learnCard.requestConsent('lc:network:network.learncard.com/trpc:contract:abc123', { redirect: true });
852
+ * const response = await learnCard.requestConsent(undefined, { redirect: true });
532
853
  * ```
533
854
  */
534
- requestConsent(contractUri: string, options?: RequestConsentOptions): Promise<ConsentResponse>;
855
+ requestConsent(contractUri?: string, options?: RequestConsentOptions): Promise<ConsentResponse>;
535
856
  /**
536
857
  * Initiate a template-based credential issuance flow
537
858
  *
@@ -552,6 +873,36 @@ declare class PartnerConnect {
552
873
  * ```
553
874
  */
554
875
  initiateTemplateIssue(templateId: string, draftRecipients?: string[]): Promise<TemplateIssueResponse>;
876
+ /**
877
+ * Request comprehensive learner context for AI tutoring systems.
878
+ *
879
+ * This method retrieves the user's credentials and personal data,
880
+ * then formats them into an LLM-ready prompt that can be injected directly into
881
+ * an AI system prompt.
882
+ *
883
+ * @param options - Configuration options for what data to include and how to format it
884
+ * @returns Promise resolving to learner context with prompt and optional raw data
885
+ *
886
+ * @example
887
+ * ```typescript
888
+ * // Get LLM-ready prompt with credentials and personal data
889
+ * const context = await learnCard.requestLearnerContext({
890
+ * includeCredentials: true,
891
+ * includePersonalData: true,
892
+ * format: 'prompt',
893
+ * instructions: 'Focus on technical skills and certifications',
894
+ * detailLevel: 'expanded'
895
+ * });
896
+ *
897
+ * // Use in AI system prompt
898
+ * const systemPrompt = `You are a helpful tutor. ${context.prompt}`;
899
+ *
900
+ * // Access structured data if needed
901
+ * console.log('User DID:', context.did);
902
+ * console.log('Credentials count:', context.raw?.credentials.length);
903
+ * ```
904
+ */
905
+ requestLearnerContext(options?: RequestLearnerContextOptions): Promise<LearnerContextResponse>;
555
906
  /**
556
907
  * Send a generic event to be processed by the brain service on behalf of this app.
557
908
  * This is used for backend-like operations such as issuing credentials.
@@ -574,6 +925,36 @@ declare class PartnerConnect {
574
925
  * ```
575
926
  */
576
927
  sendAppEvent<T = AppEventResponse>(event: AppEvent): Promise<T>;
928
+ /**
929
+ * Create and send an AI Session credential to the user.
930
+ *
931
+ * This method manages the AI Topic → AI Session hierarchy:
932
+ * - Ensures an AI Topic exists for this app (creates one if needed)
933
+ * - Creates a new AI Session as a child of the topic
934
+ * - The topic appears in the user's AI Sessions page with the app's name
935
+ * - All sessions from this app are organized under that topic
936
+ *
937
+ * @param input - Session details including title and optional metadata
938
+ * @returns Promise resolving to topic and session URIs
939
+ */
940
+ sendAiSessionCredential(input: SendAiSessionCredentialInput): Promise<SendAiSessionCredentialResponse>;
941
+ /**
942
+ * Send a notification to the current user from this app.
943
+ * The notification appears in the user's LearnCard notification inbox.
944
+ */
945
+ sendNotification(input: AppNotificationInput): Promise<AppNotificationResponse>;
946
+ /**
947
+ * Increment or decrement an app-scoped counter for the current user.
948
+ */
949
+ incrementCounter(key: string, amount: number): Promise<IncrementCounterResponse>;
950
+ /**
951
+ * Read the current value of an app-scoped counter for the current user.
952
+ */
953
+ getCounter(key: string): Promise<GetCounterResponse>;
954
+ /**
955
+ * Read multiple app-scoped counters at once for the current user.
956
+ */
957
+ getCounters(keys?: string[]): Promise<GetCountersResponse>;
577
958
  /**
578
959
  * Clean up the SDK and remove event listeners
579
960
  */
@@ -596,4 +977,4 @@ declare class PartnerConnect {
596
977
  */
597
978
  declare function createPartnerConnect(options?: PartnerConnectOptions): PartnerConnect;
598
979
 
599
- export { CheckCredentialInput, CheckCredentialResponse, CheckIssuanceStatusInput, ConsentResponse, CredentialSearchResponse, CredentialSpecificResponse, ErrorCode, GetTemplateRecipientsInput, IdentityResponse, LearnCardError, PartnerConnect, PartnerConnectOptions, PendingRequest, PostMessageRequest, PostMessageResponse, RequestConsentOptions, SendCredentialResponse, TemplateCredentialInput, TemplateCredentialResponse, TemplateIssuanceStatusResponse, TemplateIssueResponse, TemplateRecipientRecord, TemplateRecipientsResponse, VPRQuery, VerifiablePresentationRequest, createPartnerConnect, createPartnerConnect as default };
980
+ export { AppNotificationInput, AppNotificationResponse, CheckCredentialInput, CheckCredentialResponse, CheckIssuanceStatusInput, ConsentResponse, CredentialSearchResponse, CredentialSpecificResponse, ErrorCode, GetCounterResponse, GetCountersResponse, GetTemplateRecipientsInput, IdentityResponse, IncrementCounterResponse, LearnCardError, LearnerContextRawData, LearnerContextResponse, PartnerConnect, PartnerConnectError, PartnerConnectOptions, PendingRequest, PostMessageRequest, PostMessageResponse, RequestConsentOptions, RequestConsentPayload, RequestLearnerContextOptions, SendAiSessionCredentialInput, SendAiSessionCredentialResponse, SendCredentialResponse, SummaryCredentialData, SummaryCredentialKeyword, SummaryCredentialNextStep, SummaryCredentialReflection, SummaryCredentialSkill, TemplateCredentialInput, TemplateCredentialResponse, TemplateIssuanceStatusResponse, TemplateIssueResponse, TemplateRecipientRecord, TemplateRecipientsResponse, VPRQuery, VerifiablePresentationRequest, createPartnerConnect, createPartnerConnect as default };