@circuitwall/jarela 1.9.1 → 1.9.2

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 (59) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  7. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/_not-found.html +2 -2
  16. package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
  17. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  18. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/standalone/.next/server/app/page.js +3322 -3399
  24. package/.next/standalone/.next/server/app/page.js.map +1 -1
  25. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  26. package/.next/standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app-paths-manifest.json +2 -2
  28. package/.next/standalone/.next/server/chunks/1813.js.map +1 -1
  29. package/.next/standalone/.next/server/chunks/319.js.map +1 -1
  30. package/.next/standalone/.next/server/chunks/4741.js.map +1 -1
  31. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  32. package/.next/standalone/.next/server/pages/404.html +2 -2
  33. package/.next/standalone/.next/server/pages/500.html +1 -1
  34. package/.next/standalone/.next/server/proxy.js.map +1 -1
  35. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  36. package/.next/standalone/.next/static/chunks/3457-6d51726379cee3b7.js.map +1 -1
  37. package/.next/standalone/.next/static/chunks/app/{page-cd662565eba5ef59.js → page-afdef9bd1108a656.js} +3169 -3244
  38. package/.next/standalone/.next/static/chunks/app/page-afdef9bd1108a656.js.map +1 -0
  39. package/.next/standalone/.next/static/chunks/main-3eb94471f04b2368.js.map +1 -1
  40. package/.next/standalone/.next/static/css/44f9bbea39fef458.css +5 -0
  41. package/.next/standalone/.next/static/css/44f9bbea39fef458.css.map +1 -0
  42. package/.next/standalone/package.json +1 -1
  43. package/CHANGELOG.md +22 -0
  44. package/README.md +6 -6
  45. package/components/agents/agent-editor/VoiceFields.tsx +1 -1
  46. package/components/credentials/AddCredentialDialog.tsx +30 -141
  47. package/components/credentials/CredentialsPanel.tsx +145 -48
  48. package/components/{integrations/IntegrationsPanel.tsx → credentials/IntegrationCard.tsx} +9 -168
  49. package/components/documents/AddSourceForm.tsx +4 -4
  50. package/components/documents/DocumentsPanel.tsx +1 -1
  51. package/components/integrations/NetworkPanel.tsx +104 -0
  52. package/components/profile/ProfileEditor.tsx +1 -1
  53. package/components/proposals/ApprovalsBanner.tsx +2 -2
  54. package/package.json +1 -1
  55. package/.next/standalone/.next/static/chunks/app/page-cd662565eba5ef59.js.map +0 -1
  56. package/.next/standalone/.next/static/css/11aaed27d2989cc1.css +0 -5
  57. package/.next/standalone/.next/static/css/11aaed27d2989cc1.css.map +0 -1
  58. /package/.next/standalone/.next/static/{tTk-KuLcT7O-E0z6PdMmO → rr-Rxxi6kkXe1Bw8-Hzq2}/_buildManifest.js +0 -0
  59. /package/.next/standalone/.next/static/{tTk-KuLcT7O-E0z6PdMmO → rr-Rxxi6kkXe1Bw8-Hzq2}/_ssgManifest.js +0 -0
@@ -1,177 +1,18 @@
1
1
  "use client";
2
- import { CheckCircle2, ExternalLink, Filter, Key, Link as LinkIcon, Loader2, RefreshCw, Settings2, Terminal, Trash2, XCircle } from "lucide-react";
3
- import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { CheckCircle2, ExternalLink, Link as LinkIcon, Loader2, Terminal, Trash2, XCircle } from "lucide-react";
3
+ import { useEffect, useRef, useState } from "react";
4
4
  import { api } from "@/api/client";
5
- import type { IntegrationDefinition, IntegrationStatus, UserProfile } from "@/api/types";
6
- import { useDeepLinkScroll } from "@/hooks/useDeepLinkScroll";
7
- import { useAppContext } from "@/contexts/AppContext";
8
- import { PRESET_CATEGORIES } from "@/lib/integrations/categories";
9
- import { NetworkSection } from "./NetworkSection";
10
- import { AllowedSitesSection } from "./AllowedSitesSection";
11
- import { EnvAliasEditor } from "./EnvAliasEditor";
5
+ import type { IntegrationDefinition, IntegrationStatus } from "@/api/types";
12
6
 
13
7
  const SECRET_MASK = "********";
14
8
 
15
- // Human label for the preset chip in the panel header. Keep terse —
16
- // the chip is informational, not the primary picker (that lives in the
17
- // Profile editor).
18
- const PRESET_LABELS: Record<NonNullable<UserProfile["preset"]>, string> = {
19
- home: "Home",
20
- work: "Work",
21
- dev: "Developer",
22
- custom: "Everything",
23
- };
9
+ // Per-integration editor with inline OAuth Connect, Save, Test, Clear, and
10
+ // (for Gmail/Outlook) a collapsible setup guide. Extracted from the old
11
+ // `components/integrations/IntegrationsPanel.tsx` so the unified credentials
12
+ // panel can render one card per known integration — keeping OAuth and key
13
+ // editing in the same surface that handles model API keys.
24
14
 
25
- export function IntegrationsPanel() {
26
- const { dispatch } = useAppContext();
27
- const [defs, setDefs] = useState<IntegrationDefinition[]>([]);
28
- const [statuses, setStatuses] = useState<Record<string, IntegrationStatus>>({});
29
- const [loading, setLoading] = useState(true);
30
- const [syncing, setSyncing] = useState(false);
31
- const [syncMsg, setSyncMsg] = useState<string | null>(null);
32
- const [aliasEditorOpen, setAliasEditorOpen] = useState(false);
33
- const [preset, setPreset] = useState<UserProfile["preset"]>(null);
34
- const containerRef = useRef<HTMLDivElement>(null);
35
- useDeepLinkScroll("credentials", "integration", containerRef);
36
-
37
- async function load() {
38
- setLoading(true);
39
- try {
40
- const [res, profile] = await Promise.all([
41
- api.integrations.list(),
42
- api.profile.get().catch(() => null),
43
- ]);
44
- setDefs(res.definitions);
45
- setStatuses(Object.fromEntries(res.statuses.map((s) => [s.name, s])));
46
- setPreset(profile?.preset ?? null);
47
- } catch (e) { console.error(e); }
48
- finally { setLoading(false); }
49
- }
50
- useEffect(() => { void load(); }, []);
51
-
52
- // Persona filter: if the user has chosen a preset, hide integrations
53
- // outside that bucket. "custom" or unset → show everything (legacy
54
- // behaviour). Configured-but-out-of-bucket entries are kept visible
55
- // so a previously-saved credential is never silently hidden.
56
- const visibleDefs = useMemo(() => {
57
- if (!preset) return defs;
58
- const allowed = PRESET_CATEGORIES[preset];
59
- if (allowed === null) return defs;
60
- return defs.filter((def) => {
61
- if (!def.category) return true;
62
- if (allowed.has(def.category)) return true;
63
- // Don't hide something the user has already configured — bail out
64
- // gracefully so credentials never appear to vanish.
65
- return statuses[def.name]?.configured === true;
66
- });
67
- }, [defs, preset, statuses]);
68
-
69
- const hiddenCount = defs.length - visibleDefs.length;
70
-
71
- async function syncFromEnv() {
72
- setSyncing(true);
73
- setSyncMsg(null);
74
- try {
75
- const r = await api.envSync.apply();
76
- const sourceLabel = r.discovered.source === "shell-rc"
77
- ? `your ${r.discovered.shell ?? "shell"} rc`
78
- : r.discovered.source === "windows-registry"
79
- ? "your Windows User env"
80
- : "the process env";
81
- if (r.applied_count > 0) {
82
- setSyncMsg(`Synced ${r.applied_count} field(s) from ${sourceLabel}.`);
83
- } else {
84
- const userSkipped = r.candidates.filter((c) => c.action === "skipped-user").length;
85
- const equal = r.candidates.filter((c) => c.action === "skipped-equal").length;
86
- const absent = r.candidates.filter((c) => c.action === "absent").length;
87
- if (userSkipped > 0) {
88
- setSyncMsg(`Nothing to write — ${userSkipped} field(s) were edited here and won't be overwritten.`);
89
- } else if (equal > 0 && absent === r.candidates.length - equal) {
90
- setSyncMsg(`Already up to date with ${sourceLabel}.`);
91
- } else {
92
- setSyncMsg(`No matching env vars set in ${sourceLabel}.`);
93
- }
94
- }
95
- await load();
96
- } catch (e) {
97
- setSyncMsg(e instanceof Error ? e.message : String(e));
98
- } finally {
99
- setSyncing(false);
100
- }
101
- }
102
-
103
- return (
104
- <div className="flex flex-col h-full">
105
- <div className="border-b border-border px-4 py-3 flex items-center gap-2">
106
- <Key size={14} className="text-fg-subtle" />
107
- <h2 className="text-sm font-semibold text-fg mr-auto">Built-in integrations</h2>
108
- {preset && preset !== "custom" && (
109
- <button
110
- type="button"
111
- onClick={() => dispatch({ type: "SET_TAB", tab: "profile" })}
112
- title={`Filtered to "${PRESET_LABELS[preset]}" preset. Click to change in Profile.`}
113
- className="inline-flex items-center gap-1 px-2 py-1 text-[11px] rounded-full border border-border bg-surface-2 text-fg-muted hover:bg-surface-3"
114
- >
115
- <Filter size={11} />
116
- <span>{PRESET_LABELS[preset]}</span>
117
- {hiddenCount > 0 && (
118
- <span className="text-fg-faint">· {hiddenCount} hidden</span>
119
- )}
120
- </button>
121
- )}
122
- <button
123
- onClick={() => setAliasEditorOpen((v) => !v)}
124
- title="Add additional env-var name aliases that env-sync should look for, per integration field."
125
- className="inline-flex items-center gap-1 px-2 py-1 text-[11px] rounded border border-border text-fg-muted hover:bg-surface-3"
126
- >
127
- <Settings2 size={11} />
128
- Aliases
129
- </button>
130
- <button
131
- onClick={syncFromEnv}
132
- disabled={syncing}
133
- title="Pull standard credential env vars (GITHUB_TOKEN, ATLASSIAN_API_TOKEN, …) from your shell rc / Windows User env. Fields you've edited here are never overwritten."
134
- className="inline-flex items-center gap-1 px-2 py-1 text-[11px] rounded border border-border text-fg-muted hover:bg-surface-3 disabled:opacity-50"
135
- >
136
- {syncing ? <Loader2 size={11} className="animate-spin" /> : <RefreshCw size={11} />}
137
- Sync from environment
138
- </button>
139
- </div>
140
-
141
- <div ref={containerRef} className="flex-1 overflow-y-auto px-4 py-3">
142
- {syncMsg && (
143
- <div className="mb-3 px-3 py-2 rounded border border-border bg-surface-2 text-[11px] text-fg-muted flex items-start gap-2">
144
- <Terminal size={12} className="mt-0.5 text-fg-subtle shrink-0" />
145
- <span className="flex-1">{syncMsg}</span>
146
- <button onClick={() => setSyncMsg(null)} className="text-fg-faint hover:text-fg">
147
- <XCircle size={12} />
148
- </button>
149
- </div>
150
- )}
151
- {aliasEditorOpen && (
152
- <EnvAliasEditor
153
- onClose={() => setAliasEditorOpen(false)}
154
- onSaved={() => { /* re-sync happens on next click of Sync button; nothing to refresh here */ }}
155
- />
156
- )}
157
- <NetworkSection />
158
- <AllowedSitesSection />
159
- {loading && defs.length === 0 && <p className="text-fg-faint text-sm py-6 text-center">Loading…</p>}
160
- {!loading && defs.length === 0 && <p className="text-fg-faint text-sm py-6 text-center">No integrations available.</p>}
161
- {visibleDefs.map((def) => (
162
- <IntegrationCard
163
- key={def.name}
164
- definition={def}
165
- status={statuses[def.name]}
166
- onChanged={load}
167
- />
168
- ))}
169
- </div>
170
- </div>
171
- );
172
- }
173
-
174
- function IntegrationCard({
15
+ export function IntegrationCard({
175
16
  definition: def,
176
17
  status,
177
18
  onChanged,
@@ -87,10 +87,10 @@ function buildPayload(s: FormState): CreatePayload | null {
87
87
 
88
88
  function kindHint(kind: DocumentSourceKind): string {
89
89
  if (kind === "local_folder") return "Pick a folder on this machine.";
90
- if (kind === "gmail_mail") return "Requires Gmail credentials (Credentials → Built-in integrations → Gmail).";
91
- if (kind === "outlook_mail") return "Requires Outlook credentials (Credentials → Built-in integrations → Outlook).";
92
- if (isGithubKind(kind)) return "Requires GitHub credentials (Credentials → Built-in integrations → GitHub).";
93
- return "Requires Atlassian credentials (Credentials → Built-in integrations → Atlassian).";
90
+ if (kind === "gmail_mail") return "Requires Gmail credentials (Credentials → Gmail).";
91
+ if (kind === "outlook_mail") return "Requires Outlook credentials (Credentials → Outlook).";
92
+ if (isGithubKind(kind)) return "Requires GitHub credentials (Credentials → GitHub).";
93
+ return "Requires Atlassian credentials (Credentials → Atlassian).";
94
94
  }
95
95
 
96
96
  export function AddSourceForm({ disabled, onSubmit }: Props) {
@@ -31,7 +31,7 @@ export function DocumentsPanel() {
31
31
  <p className="text-xs text-fg-faint leading-relaxed">
32
32
  Sources listed here are indexed in the background. Text files in folders are chunked, embedded, and
33
33
  made available to agents via the <code className="font-mono text-fg-muted">documents_search</code> tool.
34
- Remote sources reuse credentials configured in <em>Credentials → Built-in integrations</em>:
34
+ Remote sources reuse credentials configured in <em>Credentials</em>:
35
35
  {" "}Jira/Confluence under <em>Atlassian</em>, GitHub PRs/repos under <em>GitHub</em>, and mail under <em>Gmail</em>/<em>Outlook</em>.
36
36
  Embedding uses your default model provider; without one, search falls back to substring match.
37
37
  </p>
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import { Globe, Loader2, RefreshCw, Settings2, Terminal, XCircle } from "lucide-react";
3
+ import { useRef, useState } from "react";
4
+ import { api } from "@/api/client";
5
+ import { useDeepLinkScroll } from "@/hooks/useDeepLinkScroll";
6
+ import { NetworkSection } from "./NetworkSection";
7
+ import { AllowedSitesSection } from "./AllowedSitesSection";
8
+ import { EnvAliasEditor } from "./EnvAliasEditor";
9
+
10
+ // "Network & environment" hosts everything that's NOT a credential: HTTP
11
+ // proxy, allowed sites, env-var aliases, and the env-sync button that
12
+ // pulls credential env vars (GITHUB_TOKEN, ATLASSIAN_API_TOKEN, …) from
13
+ // the user's shell rc / Windows User env into the unified credentials
14
+ // store. Per-integration auth (keys + OAuth) lives in the sibling
15
+ // Credentials sub-tab.
16
+
17
+ export function NetworkPanel() {
18
+ const [syncing, setSyncing] = useState(false);
19
+ const [syncMsg, setSyncMsg] = useState<string | null>(null);
20
+ const [aliasEditorOpen, setAliasEditorOpen] = useState(false);
21
+ const containerRef = useRef<HTMLDivElement>(null);
22
+ useDeepLinkScroll("credentials", "network", containerRef);
23
+
24
+ async function syncFromEnv() {
25
+ setSyncing(true);
26
+ setSyncMsg(null);
27
+ try {
28
+ const r = await api.envSync.apply();
29
+ const sourceLabel = r.discovered.source === "shell-rc"
30
+ ? `your ${r.discovered.shell ?? "shell"} rc`
31
+ : r.discovered.source === "windows-registry"
32
+ ? "your Windows User env"
33
+ : "the process env";
34
+ if (r.applied_count > 0) {
35
+ setSyncMsg(`Synced ${r.applied_count} field(s) from ${sourceLabel}.`);
36
+ } else {
37
+ const userSkipped = r.candidates.filter((c) => c.action === "skipped-user").length;
38
+ const equal = r.candidates.filter((c) => c.action === "skipped-equal").length;
39
+ const absent = r.candidates.filter((c) => c.action === "absent").length;
40
+ if (userSkipped > 0) {
41
+ setSyncMsg(`Nothing to write — ${userSkipped} field(s) were edited here and won't be overwritten.`);
42
+ } else if (equal > 0 && absent === r.candidates.length - equal) {
43
+ setSyncMsg(`Already up to date with ${sourceLabel}.`);
44
+ } else {
45
+ setSyncMsg(`No matching env vars set in ${sourceLabel}.`);
46
+ }
47
+ }
48
+ // Notify the Credentials list so it re-loads any newly-synced rows.
49
+ if (typeof window !== "undefined") {
50
+ window.dispatchEvent(new CustomEvent("jarela:credentials-changed"));
51
+ }
52
+ } catch (e) {
53
+ setSyncMsg(e instanceof Error ? e.message : String(e));
54
+ } finally {
55
+ setSyncing(false);
56
+ }
57
+ }
58
+
59
+ return (
60
+ <div className="flex flex-col h-full">
61
+ <div className="border-b border-border px-4 py-3 flex items-center gap-2">
62
+ <Globe size={14} className="text-fg-subtle" />
63
+ <h2 className="text-sm font-semibold text-fg mr-auto">Network &amp; environment</h2>
64
+ <button
65
+ onClick={() => setAliasEditorOpen((v) => !v)}
66
+ title="Add additional env-var name aliases that env-sync should look for, per integration field."
67
+ className="inline-flex items-center gap-1 px-2 py-1 text-[11px] rounded border border-border text-fg-muted hover:bg-surface-3"
68
+ >
69
+ <Settings2 size={11} />
70
+ Aliases
71
+ </button>
72
+ <button
73
+ onClick={syncFromEnv}
74
+ disabled={syncing}
75
+ title="Pull standard credential env vars (GITHUB_TOKEN, ATLASSIAN_API_TOKEN, …) from your shell rc / Windows User env into the Credentials list. Fields you've edited there are never overwritten."
76
+ className="inline-flex items-center gap-1 px-2 py-1 text-[11px] rounded border border-border text-fg-muted hover:bg-surface-3 disabled:opacity-50"
77
+ >
78
+ {syncing ? <Loader2 size={11} className="animate-spin" /> : <RefreshCw size={11} />}
79
+ Sync from environment
80
+ </button>
81
+ </div>
82
+
83
+ <div ref={containerRef} className="flex-1 overflow-y-auto px-4 py-3">
84
+ {syncMsg && (
85
+ <div className="mb-3 px-3 py-2 rounded border border-border bg-surface-2 text-[11px] text-fg-muted flex items-start gap-2">
86
+ <Terminal size={12} className="mt-0.5 text-fg-subtle shrink-0" />
87
+ <span className="flex-1">{syncMsg}</span>
88
+ <button onClick={() => setSyncMsg(null)} className="text-fg-faint hover:text-fg">
89
+ <XCircle size={12} />
90
+ </button>
91
+ </div>
92
+ )}
93
+ {aliasEditorOpen && (
94
+ <EnvAliasEditor
95
+ onClose={() => setAliasEditorOpen(false)}
96
+ onSaved={() => { /* re-sync happens on next click of Sync button; nothing to refresh here */ }}
97
+ />
98
+ )}
99
+ <NetworkSection />
100
+ <AllowedSitesSection />
101
+ </div>
102
+ </div>
103
+ );
104
+ }
@@ -203,7 +203,7 @@ function PresetPicker({
203
203
  <div className="block">
204
204
  <span className="text-xs text-fg-subtle mb-1 block">Persona</span>
205
205
  <p className="text-[11px] text-fg-faint mb-2">
206
- Filters the Built-in integrations panel so you only see integrations relevant
206
+ Filters the Credentials list so you only see integrations relevant
207
207
  to how you use Jarela. Pick &quot;Everything&quot; to see them all.
208
208
  </p>
209
209
  <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
@@ -14,11 +14,11 @@ function approvalToastTarget(action: PendingAction): { href: string; hrefLabel:
14
14
  switch (action.kind) {
15
15
  case "enable_integration": {
16
16
  const id = str("id");
17
- return id ? { href: `?tab=credentials&item=integrations`, hrefLabel: "Open in Credentials →", title: `${id} enabled` } : null;
17
+ return id ? { href: `?tab=credentials&item=${encodeURIComponent(id)}`, hrefLabel: "Open in Credentials →", title: `${id} enabled` } : null;
18
18
  }
19
19
  case "start_oauth": {
20
20
  const id = str("integration_id");
21
- return id ? { href: `?tab=credentials&item=integrations`, hrefLabel: "Open in Credentials →", title: `${id} authorized` } : null;
21
+ return id ? { href: `?tab=credentials&item=${encodeURIComponent(id)}`, hrefLabel: "Open in Credentials →", title: `${id} authorized` } : null;
22
22
  }
23
23
  case "set_provider_key": {
24
24
  const name = str("name") ?? str("provider");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circuitwall/jarela",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "Jarela — local chat interface for LangGraph agents (multi-provider, single-process, SQLite-backed).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Andrew Ge Wu",