@mem9/opencode 0.1.2 → 0.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tui/index.ts +59 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mem9/opencode",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OpenCode memory plugin — cloud-persistent AI agent memory with hybrid vector + keyword search via mem9",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/tui/index.ts CHANGED
@@ -3,6 +3,7 @@ import type {
3
3
  TuiCommand,
4
4
  TuiPlugin,
5
5
  TuiPluginApi,
6
+ TuiToast,
6
7
  } from "@opencode-ai/plugin/tui";
7
8
  import {
8
9
  resolveMem9Home,
@@ -61,6 +62,22 @@ interface SetupProfileOptionState {
61
62
  currentProfileId?: string;
62
63
  }
63
64
 
65
+ let hasShownVisibleApiKeyWarning = false;
66
+ const DEFAULT_TOAST_DURATION_MS = 5000;
67
+
68
+ function scheduleDialogTransition(next: () => void): void {
69
+ // Delay prompt-to-prompt transitions so the next dialog does not reuse
70
+ // the same Enter keypress that confirmed the current prompt.
71
+ setTimeout(next, 0);
72
+ }
73
+
74
+ function showToast(api: TuiPluginApi, toast: TuiToast): void {
75
+ api.ui.toast({
76
+ duration: toast.duration ?? DEFAULT_TOAST_DURATION_MS,
77
+ ...toast,
78
+ });
79
+ }
80
+
64
81
  function getProjectDir(api: TuiPluginApi): string {
65
82
  const worktree = api.state.path.worktree.trim();
66
83
  if (worktree.length > 0) {
@@ -113,7 +130,7 @@ function showError(
113
130
  retryCommand = false,
114
131
  ): void {
115
132
  const suffix = retryCommand ? " Run /mem9-setup again when you are ready to retry." : "";
116
- api.ui.toast({
133
+ showToast(api, {
117
134
  variant: "error",
118
135
  title: "mem9 setup failed",
119
136
  message: `${error instanceof Error ? error.message : String(error)}${suffix}`,
@@ -121,7 +138,7 @@ function showError(
121
138
  }
122
139
 
123
140
  function showProfileSavedSuccess(api: TuiPluginApi, profileId: string): void {
124
- api.ui.toast({
141
+ showToast(api, {
125
142
  variant: "success",
126
143
  title: "mem9 configured",
127
144
  message: `Saved profile ${profileId} and set it as the default user profile. Restart OpenCode to reload mem9.`,
@@ -134,7 +151,7 @@ function showScopeSavedSuccess(
134
151
  profileId: string,
135
152
  ): void {
136
153
  const scopeLabel = scope === "user" ? "user settings" : "project settings";
137
- api.ui.toast({
154
+ showToast(api, {
138
155
  variant: "success",
139
156
  title: "mem9 configured",
140
157
  message: `Saved ${scopeLabel} with profile ${profileId}. Restart OpenCode to reload mem9.`,
@@ -335,7 +352,7 @@ function showProfileIdDialog(
335
352
  onConfirm: (value) => {
336
353
  const next = value.trim();
337
354
  if (next.length === 0) {
338
- api.ui.toast({
355
+ showToast(api, {
339
356
  variant: "warning",
340
357
  message: "Profile ID is required.",
341
358
  });
@@ -343,7 +360,7 @@ function showProfileIdDialog(
343
360
  }
344
361
 
345
362
  if (isReusableProfileID(state, next)) {
346
- api.ui.toast({
363
+ showToast(api, {
347
364
  variant: "warning",
348
365
  message: "That profile already has credentials. Pick a new profile ID or use the existing profile in scope settings.",
349
366
  });
@@ -354,7 +371,9 @@ function showProfileIdDialog(
354
371
  if (draft.label.trim().length === 0) {
355
372
  draft.label = next === "default" ? "Personal" : next;
356
373
  }
357
- showProfileLabelDialog(api, paths, state, action, draft);
374
+ scheduleDialogTransition(() => {
375
+ showProfileLabelDialog(api, paths, state, action, draft);
376
+ });
358
377
  },
359
378
  onCancel: () => {
360
379
  showActionDialog(api, paths, state);
@@ -378,7 +397,7 @@ function showProfileLabelDialog(
378
397
  onConfirm: (value) => {
379
398
  const next = value.trim();
380
399
  if (next.length === 0) {
381
- api.ui.toast({
400
+ showToast(api, {
382
401
  variant: "warning",
383
402
  message: "Profile label is required.",
384
403
  });
@@ -386,10 +405,14 @@ function showProfileLabelDialog(
386
405
  }
387
406
 
388
407
  draft.label = next;
389
- showProfileBaseUrlDialog(api, paths, state, action, draft);
408
+ scheduleDialogTransition(() => {
409
+ showProfileBaseUrlDialog(api, paths, state, action, draft);
410
+ });
390
411
  },
391
412
  onCancel: () => {
392
- showProfileIdDialog(api, paths, state, action, draft);
413
+ scheduleDialogTransition(() => {
414
+ showProfileIdDialog(api, paths, state, action, draft);
415
+ });
393
416
  },
394
417
  }),
395
418
  );
@@ -410,7 +433,7 @@ function showProfileBaseUrlDialog(
410
433
  onConfirm: (value) => {
411
434
  const next = value.trim();
412
435
  if (next.length === 0) {
413
- api.ui.toast({
436
+ showToast(api, {
414
437
  variant: "warning",
415
438
  message: "mem9 API URL is required.",
416
439
  });
@@ -423,10 +446,14 @@ function showProfileBaseUrlDialog(
423
446
  return;
424
447
  }
425
448
 
426
- showProfileApiKeyDialog(api, paths, state, draft);
449
+ scheduleDialogTransition(() => {
450
+ showProfileApiKeyDialog(api, paths, state, draft);
451
+ });
427
452
  },
428
453
  onCancel: () => {
429
- showProfileLabelDialog(api, paths, state, action, draft);
454
+ scheduleDialogTransition(() => {
455
+ showProfileLabelDialog(api, paths, state, action, draft);
456
+ });
430
457
  },
431
458
  }),
432
459
  );
@@ -438,11 +465,14 @@ function showProfileApiKeyDialog(
438
465
  state: SetupState,
439
466
  draft: ProfileDraft,
440
467
  ): void {
441
- api.ui.toast({
442
- variant: "warning",
443
- message: "The current OpenCode prompt is plain text. Your API key stays visible while typing.",
444
- duration: 4000,
445
- });
468
+ if (!hasShownVisibleApiKeyWarning) {
469
+ hasShownVisibleApiKeyWarning = true;
470
+ showToast(api, {
471
+ variant: "warning",
472
+ message: "API key input is visible while typing.",
473
+ duration: 4000,
474
+ });
475
+ }
446
476
 
447
477
  api.ui.dialog.replace(() =>
448
478
  api.ui.DialogPrompt({
@@ -451,7 +481,7 @@ function showProfileApiKeyDialog(
451
481
  onConfirm: (value) => {
452
482
  const next = value.trim();
453
483
  if (next.length === 0) {
454
- api.ui.toast({
484
+ showToast(api, {
455
485
  variant: "warning",
456
486
  message: "mem9 API key is required.",
457
487
  });
@@ -462,7 +492,9 @@ function showProfileApiKeyDialog(
462
492
  void submitManualProfile(api, paths, draft);
463
493
  },
464
494
  onCancel: () => {
465
- showProfileBaseUrlDialog(api, paths, state, "manual-api-key", draft);
495
+ scheduleDialogTransition(() => {
496
+ showProfileBaseUrlDialog(api, paths, state, "manual-api-key", draft);
497
+ });
466
498
  },
467
499
  }),
468
500
  );
@@ -570,7 +602,7 @@ function showDefaultTimeoutDialog(
570
602
  onConfirm: (value) => {
571
603
  const parsed = parsePositiveInteger(value, "defaultTimeoutMs");
572
604
  if (parsed === null) {
573
- api.ui.toast({
605
+ showToast(api, {
574
606
  variant: "warning",
575
607
  message: "Default timeout must be a positive integer.",
576
608
  });
@@ -578,7 +610,9 @@ function showDefaultTimeoutDialog(
578
610
  }
579
611
 
580
612
  draft.defaultTimeoutMs = parsed;
581
- showSearchTimeoutDialog(api, paths, state, draft);
613
+ scheduleDialogTransition(() => {
614
+ showSearchTimeoutDialog(api, paths, state, draft);
615
+ });
582
616
  },
583
617
  onCancel: () => {
584
618
  showScopeDebugDialog(api, paths, state, draft);
@@ -601,7 +635,7 @@ function showSearchTimeoutDialog(
601
635
  onConfirm: (value) => {
602
636
  const parsed = parsePositiveInteger(value, "searchTimeoutMs");
603
637
  if (parsed === null) {
604
- api.ui.toast({
638
+ showToast(api, {
605
639
  variant: "warning",
606
640
  message: "Search timeout must be a positive integer.",
607
641
  });
@@ -612,7 +646,9 @@ function showSearchTimeoutDialog(
612
646
  void submitScopeConfigDraft(api, paths, draft);
613
647
  },
614
648
  onCancel: () => {
615
- showDefaultTimeoutDialog(api, paths, state, draft);
649
+ scheduleDialogTransition(() => {
650
+ showDefaultTimeoutDialog(api, paths, state, draft);
651
+ });
616
652
  },
617
653
  }),
618
654
  );