jerob 1.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 (69) hide show
  1. package/CLI/cli.ts +42 -0
  2. package/README.md +137 -0
  3. package/SETUP.md +584 -0
  4. package/agent/action-tracker.ts +45 -0
  5. package/agent/agent-tools.ts +111 -0
  6. package/agent/approval.ts +137 -0
  7. package/agent/diff-view.ts +26 -0
  8. package/agent/orchestrator.ts +186 -0
  9. package/agent/tool-executor.ts +463 -0
  10. package/agent/types.ts +69 -0
  11. package/ask/orchestrator.ts +244 -0
  12. package/auth/auth.ts +567 -0
  13. package/auth/config-store.ts +77 -0
  14. package/auth/crypto.ts +51 -0
  15. package/auth/env-writer.ts +82 -0
  16. package/bin/jerob.js +28 -0
  17. package/config/ai.config.ts +163 -0
  18. package/email_ops/email-tools.ts +178 -0
  19. package/email_ops/email_functions.ts +443 -0
  20. package/email_ops/email_init.ts +92 -0
  21. package/email_ops/email_pass_store.ts +61 -0
  22. package/email_ops/email_server.ts +29 -0
  23. package/email_ops/types.ts +88 -0
  24. package/index.ts +176 -0
  25. package/package.json +88 -0
  26. package/plan/browser-agent/README.md +118 -0
  27. package/plan/browser-agent/USAGE.md +308 -0
  28. package/plan/browser-agent/evaluator.ts +353 -0
  29. package/plan/browser-agent/executor.ts +372 -0
  30. package/plan/browser-agent/index.ts +13 -0
  31. package/plan/browser-agent/orchestrator.ts +323 -0
  32. package/plan/browser-agent/planner.ts +200 -0
  33. package/plan/browser-agent/types.ts +62 -0
  34. package/plan/browser-tool.ts +128 -0
  35. package/plan/index.ts +12 -0
  36. package/plan/orchestrator.ts +214 -0
  37. package/plan/planner.ts +183 -0
  38. package/plan/selection.ts +50 -0
  39. package/plan/types.ts +13 -0
  40. package/plan/web-tools.ts +119 -0
  41. package/scheduler/ARCHITECTURE.md +263 -0
  42. package/scheduler/README.md +200 -0
  43. package/scheduler/SETUP-READY.sql +84 -0
  44. package/scheduler/check-status.sql +124 -0
  45. package/scheduler/config-sync.ts +91 -0
  46. package/scheduler/db-migrate.ts +271 -0
  47. package/scheduler/db.ts +162 -0
  48. package/scheduler/debug.ts +184 -0
  49. package/scheduler/orchestrator.ts +438 -0
  50. package/scheduler/planner.ts +170 -0
  51. package/scheduler/update-task-email.ts +70 -0
  52. package/supabase/.temp/cli-latest +1 -0
  53. package/supabase/.temp/gotrue-version +1 -0
  54. package/supabase/.temp/linked-project.json +1 -0
  55. package/supabase/.temp/pooler-url +1 -0
  56. package/supabase/.temp/postgres-version +1 -0
  57. package/supabase/.temp/project-ref +1 -0
  58. package/supabase/.temp/rest-version +1 -0
  59. package/supabase/.temp/storage-migration +1 -0
  60. package/supabase/.temp/storage-version +1 -0
  61. package/supabase/deploy.ps1 +50 -0
  62. package/supabase/functions/scheduler-tick/index.ts +496 -0
  63. package/supabase/supabase/.temp/linked-project.json +1 -0
  64. package/tsconfig.json +33 -0
  65. package/tui/spinner.ts +33 -0
  66. package/tui/spinup.ts +67 -0
  67. package/tui/terminal-render.ts +16 -0
  68. package/utils/llm-error.ts +185 -0
  69. package/utils/model-validator.ts +247 -0
@@ -0,0 +1,200 @@
1
+ # Jimmy Scheduler
2
+
3
+ Fully automated task scheduler with AI planning. Tasks run either locally (`jimmy daemon`) or serverless (Supabase Edge Functions + pg_cron).
4
+
5
+ ## Features
6
+
7
+ - **AI Task Planning** — describe a task in plain English, LLM breaks it into steps + cron schedule
8
+ - **Step Types** — web_search, web_crawl, custom (pure LLM), email_send, browser (local only)
9
+ - **Auto Re-auth** — Gmail refresh token auto-syncs to Supabase on re-auth, zero manual updates
10
+ - **Run History** — all runs logged to Supabase with full step results
11
+ - **Email Summaries** — optional email digest after each run
12
+
13
+ ---
14
+
15
+ ## Setup (One Time)
16
+
17
+ ### 1. Create Supabase tables
18
+
19
+ Already done if you ran the main setup SQL. Verify these exist:
20
+ ```sql
21
+ scheduler_tasks
22
+ scheduler_runs
23
+ user_config
24
+ ```
25
+
26
+ ### 2. Deploy Edge Function (serverless mode)
27
+
28
+ ```powershell
29
+ # Install Supabase CLI
30
+ npm i -g supabase
31
+
32
+ # Login
33
+ supabase login
34
+
35
+ # Link your project
36
+ supabase link --project-ref <YOUR_PROJECT_REF>
37
+
38
+ # Deploy + sync credentials
39
+ .\supabase\deploy.ps1
40
+ ```
41
+
42
+ The script:
43
+ - Deploys the `scheduler-tick` Edge Function
44
+ - Sets `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` as secrets
45
+ - Syncs all API keys (OpenRouter, Groq, Firecrawl, Google) to `user_config` table
46
+
47
+ ### 3. Register pg_cron job
48
+
49
+ Run the SQL in `supabase/functions/scheduler-tick/setup.sql` in your Supabase SQL editor.
50
+
51
+ Replace `<PROJECT_REF>` with your actual project ref, and set your service role key in the last line.
52
+
53
+ ---
54
+
55
+ ## Usage
56
+
57
+ ### Add a Task
58
+
59
+ ```
60
+ jimmy jet → CLI → Scheduler → Add new task
61
+ ```
62
+
63
+ Describe it naturally:
64
+ > "Every morning at 9am, search for top AI news from the last 24 hours and email me a summary"
65
+
66
+ The LLM plans:
67
+ ```json
68
+ {
69
+ "name": "Daily AI News Digest",
70
+ "cron": "0 9 * * *",
71
+ "steps": [
72
+ { "order": 1, "type": "web_search", "instruction": "Find top AI news from last 24 hours" },
73
+ { "order": 2, "type": "email_send", "instruction": "Send summary to user" }
74
+ ]
75
+ }
76
+ ```
77
+
78
+ You can edit the cron expression before saving.
79
+
80
+ ### Task Management
81
+
82
+ - **List** — see all tasks, their status, run count, next run time
83
+ - **Manage** — enable/disable, edit cron/email, view run history, run now (manual trigger), delete
84
+
85
+ ### Execution Modes
86
+
87
+ **Serverless (Default)** — Tasks run in Supabase Edge Function every minute via pg_cron. Your machine can be off.
88
+
89
+ **Supported Step Types:**
90
+ - `web_search` - Search via Firecrawl API
91
+ - `web_crawl` - Scrape URLs via Firecrawl API
92
+ - `custom` - LLM-powered tasks (OpenRouter → Groq fallback)
93
+ - `email_send` - Gmail API (auto-refresh token)
94
+
95
+ ---
96
+
97
+ ## How Auto Re-auth Works
98
+
99
+ When you re-authenticate Gmail via `jimmy jet`:
100
+
101
+ 1. New refresh token saved to `~/.cccontrol/googleAuth/google_config.json`
102
+ 2. **Automatically synced to Supabase `user_config` table**
103
+ 3. Edge Function reads from `user_config` on every run
104
+ 4. No manual `supabase secrets set` needed ever again
105
+
106
+ Same applies to all API keys — update `.env` and run `jimmy sync-credentials` to push changes.
107
+
108
+ ---
109
+
110
+ ## Cron Expression Examples
111
+
112
+ | Expression | Meaning |
113
+ |---|---|
114
+ | `* * * * *` | Every minute |
115
+ | `0 * * * *` | Every hour on the hour |
116
+ | `0 9 * * *` | Every day at 9:00 AM |
117
+ | `0 9 * * 1` | Every Monday at 9:00 AM |
118
+ | `*/15 * * * *` | Every 15 minutes |
119
+ | `0 0 1 * *` | First day of every month at midnight |
120
+
121
+ ---
122
+
123
+ ## Troubleshooting
124
+
125
+ ### Quick Debug
126
+
127
+ Run the built-in debug tool:
128
+ ```powershell
129
+ jimmy scheduler-debug
130
+ ```
131
+
132
+ This checks:
133
+ - ✓ Credentials synced to Supabase
134
+ - ✓ Tasks and their run history
135
+ - ✓ Manually triggers Edge Function for testing
136
+
137
+ ### Check in Supabase Dashboard
138
+
139
+ **View Task Runs:**
140
+ ```sql
141
+ select * from scheduler_runs order by started_at desc limit 20;
142
+ ```
143
+
144
+ **Check Cron Job Status:**
145
+ ```sql
146
+ -- See if cron job is registered
147
+ select * from cron.job;
148
+
149
+ -- View recent cron executions
150
+ select * from cron.job_run_details
151
+ where jobname = 'jimmy-scheduler-tick'
152
+ order by start_time desc
153
+ limit 10;
154
+ ```
155
+
156
+ **View Tasks:**
157
+ ```sql
158
+ select id, name, enabled, cron, next_run_at, last_run_at, run_count
159
+ from scheduler_tasks;
160
+ ```
161
+
162
+ **Complete Status Check:**
163
+ Open `scheduler/check-status.sql` and copy-paste sections into Supabase SQL Editor.
164
+
165
+ ### Common Issues
166
+
167
+ **Tasks not running?**
168
+
169
+ 1. Check `supabase secrets list` — ensure `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY` are set
170
+ 2. Query `user_config` table — verify credentials are present
171
+ 3. Check `scheduler_runs` table — look for error messages
172
+ 4. View Edge Function logs: `supabase functions logs scheduler-tick`
173
+ 5. Verify pg_cron job: `select * from cron.job;`
174
+
175
+ **Gmail sends failing?**
176
+
177
+ Run `jimmy sync-credentials` to push the latest refresh token to Supabase.
178
+
179
+ **Task says "completed" but I see no output?**
180
+
181
+ Check the `scheduler_runs` table for detailed `step_results` and `output` fields.
182
+
183
+ **Cron job not registered?**
184
+
185
+ You need to run the setup SQL once: `supabase/functions/scheduler-tick/setup.sql`
186
+
187
+ ---
188
+
189
+ ## Architecture
190
+
191
+ All tasks run **100% serverless in Supabase** via Edge Functions + pg_cron. Your machine can be completely off.
192
+
193
+ **Step Types (all serverless):**
194
+
195
+ | Type | What it does |
196
+ |------|-------------|
197
+ | `web_search` | Search via Firecrawl API |
198
+ | `web_crawl` | Scrape URLs via Firecrawl API |
199
+ | `custom` | LLM-powered tasks (OpenRouter → Groq fallback) |
200
+ | `email_send` | Send via Gmail API (auto-refresh token) |
@@ -0,0 +1,84 @@
1
+ -- ════════════════════════════════════════════════════════════════════════════════
2
+ -- Jimmy Scheduler — One-Time Setup SQL
3
+ -- 1. Go to your Supabase project → SQL Editor
4
+ -- 2. Paste this entire file and click RUN
5
+ -- ════════════════════════════════════════════════════════════════════════════════
6
+
7
+ -- 1. Extensions
8
+ CREATE EXTENSION IF NOT EXISTS pg_cron;
9
+ GRANT USAGE ON SCHEMA cron TO postgres;
10
+
11
+ -- 2. scheduler_tasks table
12
+ CREATE TABLE IF NOT EXISTS scheduler_tasks (
13
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
14
+ name TEXT NOT NULL,
15
+ description TEXT NOT NULL DEFAULT '',
16
+ cron TEXT NOT NULL,
17
+ enabled BOOLEAN NOT NULL DEFAULT true,
18
+ steps JSONB NOT NULL DEFAULT '[]',
19
+ summary_email TEXT,
20
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
21
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
22
+ last_run_at TIMESTAMPTZ,
23
+ next_run_at TIMESTAMPTZ,
24
+ run_count INTEGER NOT NULL DEFAULT 0
25
+ );
26
+
27
+ -- 3. scheduler_runs table
28
+ CREATE TABLE IF NOT EXISTS scheduler_runs (
29
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
30
+ task_id UUID NOT NULL REFERENCES scheduler_tasks(id) ON DELETE CASCADE,
31
+ started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
32
+ finished_at TIMESTAMPTZ,
33
+ status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running','success','failed')),
34
+ output TEXT,
35
+ error TEXT,
36
+ step_results JSONB NOT NULL DEFAULT '[]'
37
+ );
38
+
39
+ -- 4. user_config table (stores API keys synced from your machine)
40
+ CREATE TABLE IF NOT EXISTS user_config (
41
+ key TEXT PRIMARY KEY,
42
+ value TEXT NOT NULL,
43
+ updated_at TIMESTAMPTZ DEFAULT NOW()
44
+ );
45
+
46
+ -- 5. Row Level Security — service role only (your app uses service role key)
47
+ ALTER TABLE scheduler_tasks ENABLE ROW LEVEL SECURITY;
48
+ ALTER TABLE scheduler_runs ENABLE ROW LEVEL SECURITY;
49
+ ALTER TABLE user_config ENABLE ROW LEVEL SECURITY;
50
+
51
+ DROP POLICY IF EXISTS "Service role full access" ON scheduler_tasks;
52
+ DROP POLICY IF EXISTS "Service role full access" ON scheduler_runs;
53
+ DROP POLICY IF EXISTS "Service role full access" ON user_config;
54
+
55
+ CREATE POLICY "Service role full access" ON scheduler_tasks FOR ALL USING (auth.role() = 'service_role');
56
+ CREATE POLICY "Service role full access" ON scheduler_runs FOR ALL USING (auth.role() = 'service_role');
57
+ CREATE POLICY "Service role full access" ON user_config FOR ALL USING (auth.role() = 'service_role');
58
+
59
+ -- 6. Schedule the Edge Function to fire every minute
60
+ -- Replace YOUR_PROJECT_REF and YOUR_SERVICE_ROLE_KEY below before running.
61
+ -- Get them from: Supabase Dashboard → Settings → API
62
+ SELECT cron.unschedule('jimmy-scheduler-tick') WHERE EXISTS (
63
+ SELECT 1 FROM cron.job WHERE jobname = 'jimmy-scheduler-tick'
64
+ );
65
+
66
+ SELECT cron.schedule(
67
+ 'jimmy-scheduler-tick',
68
+ '* * * * *',
69
+ $$
70
+ SELECT net.http_post(
71
+ url := 'https://YOUR_PROJECT_REF.supabase.co/functions/v1/scheduler-tick',
72
+ headers := jsonb_build_object(
73
+ 'Content-Type', 'application/json',
74
+ 'Authorization', 'Bearer YOUR_SERVICE_ROLE_KEY'
75
+ ),
76
+ body := '{}'::jsonb
77
+ );
78
+ $$
79
+ );
80
+
81
+ -- ════════════════════════════════════════════════════════════════════════════════
82
+ -- Done! Verify setup:
83
+ SELECT jobid, jobname, schedule, active FROM cron.job;
84
+ -- ════════════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,124 @@
1
+ -- ════════════════════════════════════════════════════════════════════════════════
2
+ -- Jimmy Scheduler Status Check
3
+ -- Copy and paste each section into Supabase SQL Editor to debug your scheduler
4
+ -- ════════════════════════════════════════════════════════════════════════════════
5
+
6
+ -- 1. CHECK IF PG_CRON JOB EXISTS
7
+ -- Should show: jimmy-scheduler-tick job running every minute
8
+ select
9
+ jobid,
10
+ jobname,
11
+ schedule,
12
+ active,
13
+ database
14
+ from cron.job;
15
+
16
+ -- 2. CHECK RECENT CRON JOB RUNS
17
+ -- Should show executions every minute with status and return messages
18
+ select
19
+ jobname,
20
+ runid,
21
+ status,
22
+ return_message,
23
+ start_time,
24
+ end_time
25
+ from cron.job_run_details
26
+ where jobname = 'jimmy-scheduler-tick'
27
+ order by start_time desc
28
+ limit 10;
29
+
30
+ -- 3. CHECK YOUR TASKS
31
+ -- Shows all tasks, when they should run next, and run counts
32
+ select
33
+ id,
34
+ name,
35
+ enabled,
36
+ cron,
37
+ next_run_at,
38
+ last_run_at,
39
+ run_count,
40
+ summary_email,
41
+ jsonb_array_length(steps::jsonb) as step_count
42
+ from scheduler_tasks
43
+ order by created_at desc;
44
+
45
+ -- 4. CHECK TASK RUNS (EXECUTION HISTORY)
46
+ -- Shows actual task executions with results
47
+ select
48
+ r.id,
49
+ t.name as task_name,
50
+ r.status,
51
+ r.started_at,
52
+ r.finished_at,
53
+ r.output,
54
+ r.error,
55
+ jsonb_array_length(r.step_results::jsonb) as steps_executed
56
+ from scheduler_runs r
57
+ left join scheduler_tasks t on r.task_id = t.id
58
+ order by r.started_at desc
59
+ limit 20;
60
+
61
+ -- 5. CHECK CREDENTIALS IN USER_CONFIG
62
+ -- Should show all your API keys (values hidden for security)
63
+ select
64
+ key,
65
+ case
66
+ when key like '%token%' or key like '%secret%' or key like '%key%'
67
+ then '***HIDDEN***'
68
+ else left(value, 20) || '...'
69
+ end as value_preview,
70
+ updated_at
71
+ from user_config
72
+ order by key;
73
+
74
+ -- 6. CHECK IF TASKS ARE DUE NOW
75
+ -- Shows tasks that should run in the next execution
76
+ select
77
+ id,
78
+ name,
79
+ cron,
80
+ next_run_at,
81
+ now() as current_time,
82
+ next_run_at <= now() as is_due
83
+ from scheduler_tasks
84
+ where enabled = true;
85
+
86
+ -- ════════════════════════════════════════════════════════════════════════════════
87
+ -- TROUBLESHOOTING TIPS:
88
+ -- ════════════════════════════════════════════════════════════════════════════════
89
+ --
90
+ -- ❌ No rows in cron.job?
91
+ -- → You haven't run the setup SQL yet. Run: supabase/functions/scheduler-tick/setup.sql
92
+ --
93
+ -- ❌ cron.job exists but no runs in job_run_details?
94
+ -- → Check if pg_cron is enabled: CREATE EXTENSION IF NOT EXISTS pg_cron;
95
+ -- → Check Edge Function logs: supabase functions logs scheduler-tick
96
+ --
97
+ -- ❌ Cron runs but scheduler_runs table is empty?
98
+ -- → Edge Function might be failing. Check logs with: supabase functions logs scheduler-tick
99
+ -- → Verify secrets are set: supabase secrets list
100
+ --
101
+ -- ❌ scheduler_runs shows "failed" status?
102
+ -- → Check the error column in scheduler_runs
103
+ -- → Check if credentials are in user_config (query #5 above)
104
+ -- → Run: jimmy sync-credentials
105
+ --
106
+ -- ❌ Task says "completed" but no email sent?
107
+ -- → Check if summary_email is set on the task
108
+ -- → Verify google_refresh_token is in user_config
109
+ -- → Check step_results in scheduler_runs for email step errors
110
+ --
111
+ -- ════════════════════════════════════════════════════════════════════════════════
112
+
113
+ -- MANUAL EDGE FUNCTION TRIGGER (for testing):
114
+ -- Replace <PROJECT_REF> and <SERVICE_ROLE_KEY> with your actual values
115
+ /*
116
+ select net.http_post(
117
+ url := 'https://<PROJECT_REF>.supabase.co/functions/v1/scheduler-tick',
118
+ headers := jsonb_build_object(
119
+ 'Content-Type', 'application/json',
120
+ 'Authorization', 'Bearer <SERVICE_ROLE_KEY>'
121
+ ),
122
+ body := '{}'::jsonb
123
+ ) as response;
124
+ */
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Syncs local credentials to Supabase user_config table.
3
+ * The Edge Function reads from here instead of static secrets,
4
+ * so re-auth is fully automatic — no manual `supabase secrets set` needed.
5
+ *
6
+ * Required SQL (run once in Supabase SQL editor):
7
+ * create table user_config (
8
+ * key text primary key,
9
+ * value text not null,
10
+ * updated_at timestamptz default now()
11
+ * );
12
+ */
13
+
14
+ import { createClient } from "@supabase/supabase-js";
15
+
16
+ function getDb() {
17
+ const url = process.env.SUPABASE_URL;
18
+ const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_KEY;
19
+ if (!url || !key) return null;
20
+ return createClient(url, key);
21
+ }
22
+
23
+ /** Upsert a single key-value pair into user_config */
24
+ async function upsertConfig(key: string, value: string): Promise<void> {
25
+ const db = getDb();
26
+ if (!db) return; // Supabase not configured yet, skip silently
27
+ const { error } = await db.from("user_config").upsert(
28
+ { key, value, updated_at: new Date().toISOString() },
29
+ { onConflict: "key" }
30
+ );
31
+ if (error) {
32
+ // Non-fatal — log but don't crash the auth flow
33
+ console.warn(`[config-sync] Failed to sync "${key}": ${error.message}`);
34
+ }
35
+ }
36
+
37
+ /** Read a value from user_config */
38
+ export async function readRemoteConfig(key: string): Promise<string | null> {
39
+ const db = getDb();
40
+ if (!db) return null;
41
+ const { data, error } = await db
42
+ .from("user_config")
43
+ .select("value")
44
+ .eq("key", key)
45
+ .single();
46
+ if (error || !data) return null;
47
+ return (data as any).value as string;
48
+ }
49
+
50
+ /**
51
+ * Called after Gmail OAuth completes.
52
+ * Pushes the new refresh token to Supabase so the Edge Function picks it up automatically.
53
+ */
54
+ export async function syncGoogleRefreshToken(refreshToken: string): Promise<void> {
55
+ await upsertConfig("google_refresh_token", refreshToken);
56
+ }
57
+
58
+ /**
59
+ * Sync all relevant env vars to Supabase user_config.
60
+ * Call this on first deploy or when API keys change.
61
+ * Keys stored: openrouter_key, groq_api_key, firecrawl_key,
62
+ * google_client_id, google_client_secret, google_refresh_token
63
+ */
64
+ export async function syncAllSecrets(): Promise<void> {
65
+ const pairs: [string, string | undefined][] = [
66
+ ["openrouter_key", process.env.OPENROUTER_KEY],
67
+ ["openrouter_model", process.env.OPENROUTER_MODEL],
68
+ ["groq_api_key", process.env.GROQ_API_KEY],
69
+ ["firecrawl_key", process.env.FIRECRAWL_KEY],
70
+ ["google_api_key", process.env.GOOGLE_GENERATIVE_AI_API_KEY], // for Gemini in Edge Function
71
+ ["google_client_id", process.env.GOOGLE_CLIENT_ID],
72
+ ["google_client_secret", process.env.GOOGLE_CLIENT_SECRET],
73
+ ];
74
+
75
+ // Also sync Google refresh token from local file if it exists
76
+ try {
77
+ const { loadConfig } = await import("../email_ops/email_pass_store");
78
+ const googleConfig = loadConfig();
79
+ if (googleConfig?.refresh_token) {
80
+ pairs.push(["google_refresh_token", googleConfig.refresh_token]);
81
+ }
82
+ } catch (e) {
83
+ console.warn("[config-sync] Could not load Google refresh token from local file");
84
+ }
85
+
86
+ await Promise.allSettled(
87
+ pairs
88
+ .filter(([, v]) => v)
89
+ .map(([k, v]) => upsertConfig(k, v!))
90
+ );
91
+ }