alt-plugin-sdk 0.2.2 → 0.2.3
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/llms.txt +93 -38
- package/package.json +1 -1
package/llms.txt
CHANGED
|
@@ -193,8 +193,8 @@ major `1` semantics until you opt in.
|
|
|
193
193
|
## 5. Permissions
|
|
194
194
|
|
|
195
195
|
A plugin only gets exactly what its manifest declares. Calling a method
|
|
196
|
-
without its permission throws `Plugin
|
|
197
|
-
IPC boundary and never touches the database, file system, or models.
|
|
196
|
+
without its permission throws `Plugin permission required: {permission}` at
|
|
197
|
+
the IPC boundary and never touches the database, file system, or models.
|
|
198
198
|
|
|
199
199
|
| Permission | Unlocks |
|
|
200
200
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -205,13 +205,14 @@ IPC boundary and never touches the database, file system, or models.
|
|
|
205
205
|
| `notes:write` | `alt.notes.create / update / delete / setMemo / setSummary / appendTranscriptLine / upsertComponent / deleteComponent` |
|
|
206
206
|
| `notes:select` | `alt.notes.select(...)` — focuses a note in Alt's main window |
|
|
207
207
|
| `folders:write` | `alt.folders.create / rename / move / delete` |
|
|
208
|
-
| `ai:chat` | `alt.ai.
|
|
208
|
+
| `ai:chat` | `alt.ai.stream / chat.stream / complete / summarize`. (Note: `alt.ai.models.list` is NOT gated — any plugin can call it.) |
|
|
209
209
|
| `recording:control` | `alt.recording.start / stop / getStatus` |
|
|
210
210
|
| `transcription:run` | `alt.transcription.transcribeFile / transcribeNote` |
|
|
211
211
|
| `files:read` | `alt.files.list / read` |
|
|
212
212
|
| `files:write` | `alt.files.attach / delete` |
|
|
213
213
|
| `settings:read` | `alt.settings.get / list` |
|
|
214
214
|
| `actions:notes` | **Legacy.** Old plugins that declare this auto-get `notes:write` + `notes:select`. New plugins should declare the modern names. |
|
|
215
|
+
| `settings:write` | **Reserved.** Accepted by the manifest schema for forward compatibility, but no method currently requires it. Don't declare it. |
|
|
215
216
|
|
|
216
217
|
Declare the **minimum** permissions you need. Users see the list before
|
|
217
218
|
install and can later disable a plugin if its permissions feel off.
|
|
@@ -232,9 +233,12 @@ keeps working even if the host bridge is initialized after your code starts.
|
|
|
232
233
|
|
|
233
234
|
### 6.1 `alt.storage` — JSON KV per plugin
|
|
234
235
|
|
|
235
|
-
Plugin-scoped, persisted to
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
Plugin-scoped, persisted to Alt's SQLite database (in the `plugin_storage`
|
|
237
|
+
table, one row per `(pluginId, key)`). Values are deep JSON: strings,
|
|
238
|
+
numbers, booleans, `null`, arrays, and plain objects. No binary, no
|
|
239
|
+
Map/Set, no circular refs. Large blobs belong in `alt.files.attach` —
|
|
240
|
+
the storage row is JSON-encoded and meant for small structured state
|
|
241
|
+
(preferences, indices, drafts).
|
|
238
242
|
|
|
239
243
|
```ts
|
|
240
244
|
type Counter = { count: number; updatedAt: string };
|
|
@@ -300,7 +304,7 @@ Event types and their payload (`PluginHostEventPayload` union):
|
|
|
300
304
|
| `folderCreated` | `PluginFolderNode` | New folder |
|
|
301
305
|
| `folderUpdated` | `PluginFolderNode` | Folder renamed or moved |
|
|
302
306
|
| `folderDeleted` | `{ folderId }` | Folder removed |
|
|
303
|
-
| `componentUpdated` | `PluginNoteComponentSummary` | Component (memo/summary/transcript/etc)
|
|
307
|
+
| `componentUpdated` | `PluginNoteComponentSummary` | Component (memo/summary/transcript/etc) created or updated. Note: NOT fired on `deleteComponent` — there's no `componentDeleted` event today; subscribe to `noteUpdated` or re-list if you need to track deletions. |
|
|
304
308
|
| `settingChanged` | `{ key, value }` | One of the curated app settings updated |
|
|
305
309
|
| `recordingLevel` | `{ micDb, systemDb, timestamp }` | Reserved — not currently emitted |
|
|
306
310
|
|
|
@@ -445,8 +449,12 @@ Limits:
|
|
|
445
449
|
- `markdown` body (memo/summary) — ≤ 500,000 chars.
|
|
446
450
|
- `appendTranscriptLine.text` — 1–10,000 chars.
|
|
447
451
|
- `upsertComponent.contentText` — ≤ 1,000,000 chars.
|
|
448
|
-
- `setMemo` / `setSummary` / `appendTranscriptLine`
|
|
449
|
-
|
|
452
|
+
- `setMemo` / `setSummary` / `appendTranscriptLine` return `{ componentId }`
|
|
453
|
+
of the (possibly newly created) component.
|
|
454
|
+
- `upsertComponent` returns the full `PluginNoteComponentSummary` (`id`,
|
|
455
|
+
`noteId`, `componentType`, `title`, `displayOrder`, `hasFile`,
|
|
456
|
+
`createdAt`, `updatedAt`) of the upserted component — not just
|
|
457
|
+
`{ componentId }`.
|
|
450
458
|
|
|
451
459
|
### 6.5 `alt.folders` — manage the note tree
|
|
452
460
|
|
|
@@ -531,11 +539,25 @@ fileHandle.cancel();
|
|
|
531
539
|
|
|
532
540
|
Error codes (`PluginTranscriptionStreamErrorCode`):
|
|
533
541
|
|
|
534
|
-
- `FORBIDDEN` — plugin lacks `transcription:run
|
|
535
|
-
- `INVALID_REQUEST` —
|
|
536
|
-
- `
|
|
537
|
-
|
|
538
|
-
|
|
542
|
+
- `FORBIDDEN` — plugin lacks `transcription:run`.
|
|
543
|
+
- `INVALID_REQUEST` — request fails schema validation (missing/empty
|
|
544
|
+
`filePath`, non-positive `noteId`, etc.). Note: this is shape-only —
|
|
545
|
+
the host does NOT pre-check that a `filePath` exists; a non-existent
|
|
546
|
+
or unreadable path surfaces later as `PIPELINE_ERROR`.
|
|
547
|
+
- `RECORDING_ACTIVE` — Alt is currently recording; can't run transcription.
|
|
548
|
+
- `FILE_UNAVAILABLE` — only from `transcribeNote`. Fires when the target
|
|
549
|
+
note has no `recording` component or the file_inode can't be resolved.
|
|
550
|
+
A deleted file passed to `transcribeFile` does NOT fire this code —
|
|
551
|
+
it surfaces as `PIPELINE_ERROR` (the Whisper pipeline can't open it).
|
|
552
|
+
- `PIPELINE_ERROR` — Whisper/diarization pipeline crashed, OR the file
|
|
553
|
+
couldn't be opened (see `FILE_UNAVAILABLE` note above).
|
|
554
|
+
|
|
555
|
+
Also note the defaults the host applies when you omit optional fields:
|
|
556
|
+
|
|
557
|
+
- `language` falls back to the user's `transcription.lectureLanguage`
|
|
558
|
+
setting (typically `'en'`). You don't need to pass it unless you want
|
|
559
|
+
to override.
|
|
560
|
+
- `diarization` defaults to `false` when omitted.
|
|
539
561
|
|
|
540
562
|
### 6.8 `alt.ai` — chat completions and summarize
|
|
541
563
|
|
|
@@ -584,8 +606,11 @@ const handle = await alt.ai.chat.stream(
|
|
|
584
606
|
);
|
|
585
607
|
// handle.cancel() to abort.
|
|
586
608
|
|
|
587
|
-
// Run Alt's own summarize prompt against a note.
|
|
588
|
-
//
|
|
609
|
+
// Run Alt's own summarize prompt against a note. The host composes the
|
|
610
|
+
// prompt from up to three sources on the note — memo, transcript, and any
|
|
611
|
+
// existing summary (so re-running it refines an earlier output rather than
|
|
612
|
+
// starting from scratch). Each is included only if non-empty. Throws if
|
|
613
|
+
// all three are empty. Runs through the chosen model, returns markdown.
|
|
589
614
|
const sum = await alt.ai.summarize({
|
|
590
615
|
noteId,
|
|
591
616
|
style: 'Focus on definitions and worked examples.',
|
|
@@ -619,38 +644,48 @@ const bytes: ArrayBuffer = await someFile.arrayBuffer();
|
|
|
619
644
|
const attached = await alt.files.attach({
|
|
620
645
|
noteId,
|
|
621
646
|
fileName: 'lecture12.pdf',
|
|
622
|
-
data: bytes, // max 256
|
|
647
|
+
data: bytes, // max 256 MiB (256 * 1024 * 1024 bytes)
|
|
623
648
|
mimeType: 'application/pdf',
|
|
624
649
|
componentType: 'slides', // 'slides' | 'recording'
|
|
625
650
|
title: 'Lecture 12 — Slides',
|
|
626
651
|
displayOrder: 0,
|
|
627
652
|
});
|
|
628
653
|
// attached: { componentId, fileId, fileName, mimeType, sizeBytes }
|
|
654
|
+
// mimeType is `string | null` — null when the caller omitted it.
|
|
629
655
|
|
|
630
656
|
const list = await alt.files.list({ noteId });
|
|
631
657
|
const blob = await alt.files.read({ fileId: list[0].fileId });
|
|
632
|
-
// { fileName, mimeType, sizeBytes, data: ArrayBuffer }
|
|
658
|
+
// { fileName, mimeType: string | null, sizeBytes, data: ArrayBuffer }
|
|
633
659
|
|
|
634
660
|
await alt.files.delete({ componentId: attached.componentId });
|
|
635
661
|
```
|
|
636
662
|
|
|
637
663
|
Constraints:
|
|
638
664
|
|
|
639
|
-
- `fileName` is
|
|
640
|
-
|
|
641
|
-
|
|
665
|
+
- `fileName` is 1–260 chars and must match `^[^\\/\x00\n\r:*?"<>|]+$` —
|
|
666
|
+
blocks `\ / : * ? " < > |` and the NUL/LF/CR control bytes (Windows
|
|
667
|
+
reserved chars plus path separators). Other control bytes are
|
|
668
|
+
technically allowed by this regex but are still a bad idea. Host
|
|
669
|
+
re-validates; bad names throw.
|
|
642
670
|
- `componentType` is restricted to `slides | recording` because the database
|
|
643
671
|
only allows file-backed components for those types. Use
|
|
644
672
|
`notes.upsertComponent` for text-based components instead.
|
|
645
|
-
- `data` is an `ArrayBuffer` capped at 256 MiB.
|
|
673
|
+
- `data` is an `ArrayBuffer` capped at 256 MiB (`256 * 1024 * 1024`).
|
|
646
674
|
|
|
647
675
|
### 6.10 `alt.settings` — read curated app settings
|
|
648
676
|
|
|
649
|
-
Permission: `settings:read`. Read-only allowlist
|
|
650
|
-
|
|
677
|
+
Permission: `settings:read`. Read-only allowlist — there is no
|
|
678
|
+
write surface on the host today (the `settings:write` permission is
|
|
679
|
+
reserved but unused). If you need plugin-owned settings, use
|
|
680
|
+
`alt.storage`.
|
|
651
681
|
|
|
652
682
|
```ts
|
|
653
|
-
const theme = await alt.settings.get('theme');
|
|
683
|
+
const theme = await alt.settings.get('theme');
|
|
684
|
+
// theme is typed `PluginAppSettingValue` = `string | number | boolean | null`.
|
|
685
|
+
// The SDK does NOT narrow per-key — you have to check at runtime.
|
|
686
|
+
// Typical values seen here: 'system' | 'light' | 'dark'. `null` if unset.
|
|
687
|
+
if (typeof theme === 'string') applyTheme(theme);
|
|
688
|
+
|
|
654
689
|
const all = await alt.settings.list();
|
|
655
690
|
// {
|
|
656
691
|
// theme: 'dark',
|
|
@@ -694,9 +729,13 @@ const res = await altFetch('https://alt-plugin.invalid/v1/chat/completions', {
|
|
|
694
729
|
// Standard streaming Response — use res.body.getReader() or res.text().
|
|
695
730
|
```
|
|
696
731
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
exfiltrate them.
|
|
732
|
+
Headers are filtered twice on the way to the cloud. First in the SDK,
|
|
733
|
+
`Authorization` and `x-machine-id` are stripped so AI-SDK clients that
|
|
734
|
+
auto-inject API keys can't exfiltrate them. Then the host applies a
|
|
735
|
+
strict allowlist: only `accept` and `content-type` survive — everything
|
|
736
|
+
else (custom routing hints, request IDs, telemetry headers) is silently
|
|
737
|
+
dropped. Don't rely on passing arbitrary headers through `createAltFetch`;
|
|
738
|
+
put any plugin-side metadata in the request body instead.
|
|
700
739
|
|
|
701
740
|
### `createAltProvider(options?)`
|
|
702
741
|
|
|
@@ -731,8 +770,11 @@ Use this whenever you'd normally instantiate `@ai-sdk/openai` etc. Plugins
|
|
|
731
770
|
|
|
732
771
|
### 8.1 Live transcript subscriber
|
|
733
772
|
|
|
734
|
-
Watch active-note changes, fetch its transcript whenever it updates,
|
|
735
|
-
the last 10 lines.
|
|
773
|
+
Watch active-note changes, fetch its transcript whenever it updates,
|
|
774
|
+
render the last 10 transcript lines. Note `getContent().transcript` is
|
|
775
|
+
the LLM-formatted plaintext (`[m:ss | Speaker] text` per line) — slicing
|
|
776
|
+
by `\n` is fine here since each entry is exactly one line, but if you
|
|
777
|
+
need structured timing data go through §8.5 instead.
|
|
736
778
|
|
|
737
779
|
```ts
|
|
738
780
|
// permissions: ['notes:read', 'events:subscribe', 'appState:read']
|
|
@@ -749,21 +791,33 @@ const refresh = async (noteId: number) => {
|
|
|
749
791
|
render(lines);
|
|
750
792
|
};
|
|
751
793
|
|
|
752
|
-
|
|
794
|
+
// IMPORTANT: hold on to the unsubscribe handles. Leaked subscriptions
|
|
795
|
+
// stay alive for the lifetime of the plugin window. Call them in your
|
|
796
|
+
// teardown path (e.g. `beforeunload`, your framework's unmount hook).
|
|
797
|
+
const unsubActive = await alt.events.subscribe('activeNoteChanged', note => {
|
|
753
798
|
currentNoteId = note?.id ?? null;
|
|
754
799
|
if (currentNoteId !== null) void refresh(currentNoteId);
|
|
755
800
|
else render(['(no active note)']);
|
|
756
801
|
});
|
|
757
802
|
|
|
758
|
-
await alt.events.subscribe(
|
|
759
|
-
|
|
760
|
-
})
|
|
803
|
+
const unsubTranscript = await alt.events.subscribe(
|
|
804
|
+
'transcriptUpdated',
|
|
805
|
+
({ noteId }) => {
|
|
806
|
+
if (noteId === currentNoteId) void refresh(noteId);
|
|
807
|
+
},
|
|
808
|
+
);
|
|
761
809
|
|
|
762
810
|
const active = await alt.state.getActiveNoteSummary();
|
|
763
811
|
if (active) {
|
|
764
812
|
currentNoteId = active.id;
|
|
765
813
|
await refresh(active.id);
|
|
766
814
|
}
|
|
815
|
+
|
|
816
|
+
// On teardown:
|
|
817
|
+
window.addEventListener('beforeunload', () => {
|
|
818
|
+
void unsubActive();
|
|
819
|
+
void unsubTranscript();
|
|
820
|
+
});
|
|
767
821
|
```
|
|
768
822
|
|
|
769
823
|
### 8.2 Quiz from a note (AI + storage)
|
|
@@ -800,7 +854,9 @@ export async function buildFlashcards(noteId: number) {
|
|
|
800
854
|
### 8.3 Transcribe an arbitrary audio file, then save as a new note
|
|
801
855
|
|
|
802
856
|
```ts
|
|
803
|
-
// permissions: ['notes:write', 'transcription:run'
|
|
857
|
+
// permissions: ['notes:write', 'transcription:run']
|
|
858
|
+
// Note: no `notes:select` needed — the host auto-focuses freshly created
|
|
859
|
+
// notes in the main window as part of `notes.create`.
|
|
804
860
|
import { alt } from 'alt-plugin-sdk';
|
|
805
861
|
|
|
806
862
|
export async function importAudio(filePath: string, title: string) {
|
|
@@ -823,7 +879,6 @@ export async function importAudio(filePath: string, title: string) {
|
|
|
823
879
|
title: 'Imported transcript',
|
|
824
880
|
contentText: segments.join('\n'),
|
|
825
881
|
});
|
|
826
|
-
await alt.notes.select({ noteId });
|
|
827
882
|
},
|
|
828
883
|
onError: err => console.error(err.code, err.message),
|
|
829
884
|
},
|
|
@@ -834,7 +889,7 @@ export async function importAudio(filePath: string, title: string) {
|
|
|
834
889
|
### 8.4 Attach a PDF chosen via `<input type="file">`
|
|
835
890
|
|
|
836
891
|
```ts
|
|
837
|
-
// permissions: ['files:write']
|
|
892
|
+
// permissions: ['files:write', 'appState:read']
|
|
838
893
|
import { alt } from 'alt-plugin-sdk';
|
|
839
894
|
|
|
840
895
|
document.querySelector<HTMLInputElement>('#picker')!.addEventListener(
|
|
@@ -1002,7 +1057,7 @@ The npm package follows semver:
|
|
|
1002
1057
|
also subscribe to. Polling `getStatus()` 10× a second is wasteful when
|
|
1003
1058
|
`recordingStatusChanged` exists.
|
|
1004
1059
|
- **Cap your storage.** `alt.storage` is plugin-scoped but lives in the
|
|
1005
|
-
user's
|
|
1060
|
+
user's SQLite database on disk. Anything over a few MB belongs in
|
|
1006
1061
|
`alt.files.attach`.
|
|
1007
1062
|
- **Plate JSON is not exposed.** Memo/summary content is round-tripped
|
|
1008
1063
|
through Alt's internal Plate-rich-text codec; the SDK gives you the
|