// ==UserScript==
// @name STUDY//OS
// @namespace study-os
// @version 1.3.0
// @description STUDY//OS educational AI multitool
// @author STUDY//OS
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @connect dzpmqkxijesvszrdnlxy.supabase.co
// @run-at document-idle
// ==/UserScript==
(function () {
"use strict";
console.log("[STUDY//OS] Starting...");
/* =========================================================
CONFIG
========================================================= */
const CONFIG = {
SUPABASE_URL:
"https://dzpmqkxijesvszrdnlxy.supabase.co",
// PUT YOUR SUPABASE PUBLISHABLE / ANON KEY HERE
SUPABASE_KEY:
"sb_publishable_emyadr4a280vmeKErM5nOQ_MAN-JKUN",
FUNCTION:
"/functions/v1/quick-task",
MODEL:
"openrouter/free",
VERSION:
"1.3.0"
};
/* =========================================================
STATE
========================================================= */
const state = {
host: null,
shadow: null,
panel: null,
launcher: null,
open: false,
loading: false,
feature: "explain",
accessKey:
GM_getValue(
"study_os_access_key",
""
)
};
/* =========================================================
FEATURES
========================================================= */
const FEATURES = [
{
id: "explain",
icon: "✦",
title: "EXPLAIN",
description: "Understand a concept"
},
{
id: "writing",
icon: "✎",
title: "WRITING",
description: "Improve your writing"
},
{
id: "math",
icon: "∑",
title: "MATH",
description: "Step-by-step help"
},
{
id: "quiz",
icon: "?",
title: "QUIZ",
description: "Create practice questions"
},
{
id: "summary",
icon: "▤",
title: "SUMMARY",
description: "Make study notes"
},
{
id: "custom",
icon: "⌘",
title: "ASK AI",
description: "Ask anything"
}
];
/* =========================================================
DOM HELPERS
========================================================= */
function create(
tag,
options = {}
) {
const element =
document.createElement(tag);
if (options.className) {
element.className =
options.className;
}
if (options.id) {
element.id =
options.id;
}
if (options.text !== undefined) {
element.textContent =
options.text;
}
if (options.type) {
element.type =
options.type;
}
if (options.placeholder) {
element.placeholder =
options.placeholder;
}
if (options.title) {
element.title =
options.title;
}
return element;
}
function append(
parent,
...children
) {
children.forEach(
child => {
if (child) {
parent.appendChild(
child
);
}
}
);
return parent;
}
function $(selector) {
if (!state.shadow) {
return null;
}
return state.shadow.querySelector(
selector
);
}
/* =========================================================
STYLES
========================================================= */
function installStyles() {
const style =
create("style");
style.textContent = `
* {
box-sizing: border-box;
}
:host {
all: initial;
}
.sos-launcher {
position: fixed;
right: 24px;
bottom: 24px;
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
z-index: 2147483647;
border: 4px solid #000;
background: #ff2bd6;
color: #000;
font-family:
Arial,
Helvetica,
sans-serif;
font-size: 29px;
font-weight: 900;
cursor: pointer;
box-shadow:
8px 8px 0 #000;
user-select: none;
transition:
transform .1s ease,
box-shadow .1s ease;
}
.sos-launcher:hover {
transform:
translate(-3px, -3px);
box-shadow:
11px 11px 0 #000;
}
.sos-launcher:active {
transform:
translate(5px, 5px);
box-shadow:
3px 3px 0 #000;
}
.sos-panel {
position: fixed;
right: 24px;
bottom: 24px;
width: 450px;
height: 730px;
max-width:
calc(100vw - 20px);
max-height:
calc(100vh - 20px);
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 2147483647;
border: 4px solid #000;
background: #ededed;
color: #000;
box-shadow:
12px 12px 0 #000;
font-family:
Arial,
Helvetica,
sans-serif;
}
.sos-header {
min-height: 68px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: #000;
color: #fff;
}
.sos-logo-main {
font-size: 22px;
font-weight: 1000;
letter-spacing: -1.5px;
}
.sos-logo-sub {
margin-top: 3px;
color: #ff2bd6;
font-family: monospace;
font-size: 8px;
font-weight: 700;
}
.sos-header-right {
display: flex;
align-items: center;
gap: 10px;
}
.sos-status {
display: flex;
align-items: center;
gap: 5px;
font-family: monospace;
font-size: 8px;
}
.sos-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #00ff66;
border: 1px solid #fff;
}
.sos-close {
width: 32px;
height: 32px;
padding: 0;
border: 2px solid #fff;
background: #ff2bd6;
color: #000;
font-size: 22px;
font-weight: 900;
cursor: pointer;
}
.sos-content {
flex: 1;
overflow-y: auto;
padding: 12px;
}
.sos-box {
margin-bottom: 12px;
border: 2px solid #000;
background: #fff;
}
.sos-box-title {
padding: 7px 9px;
background: #000;
color: #fff;
font-family: monospace;
font-size: 9px;
font-weight: 900;
letter-spacing: .8px;
}
.sos-input-wrap {
padding: 9px;
}
.sos-material {
width: 100%;
height: 145px;
resize: vertical;
padding: 10px;
border: 2px solid #000;
outline: none;
background: #fff;
color: #000;
font-family: monospace;
font-size: 11px;
line-height: 1.45;
}
.sos-material:focus {
outline: 3px solid #ff2bd6;
outline-offset: -3px;
}
.sos-char-count {
margin-top: 5px;
text-align: right;
color: #666;
font-family: monospace;
font-size: 8px;
}
.sos-tools {
display: grid;
grid-template-columns:
repeat(2, 1fr);
gap: 7px;
padding: 8px;
}
.sos-tool {
min-height: 60px;
padding: 8px;
border: 2px solid #000;
background: #fff;
color: #000;
text-align: left;
cursor: pointer;
font-family:
Arial,
sans-serif;
font-size: 10px;
font-weight: 900;
}
.sos-tool:hover {
background: #ff2bd6;
}
.sos-tool.active {
background: #ff2bd6;
box-shadow:
3px 3px 0 #000;
}
.sos-tool-icon {
margin-right: 5px;
font-size: 17px;
}
.sos-tool-description {
display: block;
margin-top: 5px;
color: #555;
font-family: monospace;
font-size: 8px;
font-weight: 400;
}
.sos-custom {
display: none;
padding: 9px;
}
.sos-custom textarea {
width: 100%;
height: 75px;
resize: vertical;
padding: 9px;
border: 2px solid #000;
outline: none;
font-family: monospace;
font-size: 10px;
}
.sos-run {
width: 100%;
margin-bottom: 12px;
padding: 14px;
border: 3px solid #000;
background: #ff2bd6;
color: #000;
cursor: pointer;
font-size: 13px;
font-weight: 1000;
box-shadow:
5px 5px 0 #000;
}
.sos-run:hover {
transform:
translate(-2px, -2px);
box-shadow:
7px 7px 0 #000;
}
.sos-run:disabled {
opacity: .6;
cursor: wait;
}
.sos-output {
min-height: 150px;
max-height: 330px;
overflow-y: auto;
padding: 11px;
white-space: pre-wrap;
word-break: break-word;
font-size: 12px;
line-height: 1.55;
}
.sos-access {
padding: 9px;
}
.sos-access-status {
margin-bottom: 7px;
font-family: monospace;
font-size: 9px;
}
.sos-access-input {
width: 100%;
padding: 10px;
border: 2px solid #000;
outline: none;
font-family: monospace;
font-size: 10px;
}
.sos-save {
width: 100%;
margin-top: 7px;
padding: 9px;
border: 2px solid #000;
background: #fff;
color: #000;
cursor: pointer;
font-size: 10px;
font-weight: 900;
}
.sos-save:hover {
background: #ff2bd6;
}
.sos-footer {
min-height: 35px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 9px;
border-top: 3px solid #000;
background: #fff;
font-family: monospace;
font-size: 8px;
}
.sos-footer button {
padding: 0;
border: 0;
background: transparent;
color: #000;
cursor: pointer;
font-family: monospace;
font-size: 8px;
text-decoration: underline;
}
.sos-toast {
position: fixed;
right: 24px;
bottom: 100px;
max-width: 330px;
padding: 11px 14px;
z-index: 2147483647;
border: 2px solid #ff2bd6;
background: #000;
color: #fff;
box-shadow:
5px 5px 0 #ff2bd6;
font-family: monospace;
font-size: 10px;
}
.sos-toast.error {
border-color: #ff3333;
box-shadow:
5px 5px 0 #ff3333;
}
.sos-toast.success {
border-color: #00ff66;
box-shadow:
5px 5px 0 #00ff66;
}
.sos-spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 3px solid #ddd;
border-top-color: #000;
border-radius: 50%;
animation:
sos-spin .7s linear infinite;
}
@keyframes sos-spin {
to {
transform:
rotate(360deg);
}
}
@media(max-width: 500px) {
.sos-panel {
right: 8px;
bottom: 8px;
width:
calc(100vw - 16px);
height:
calc(100vh - 16px);
}
.sos-launcher {
right: 14px;
bottom: 14px;
}
}
`;
state.shadow.appendChild(
style
);
}
/* =========================================================
ROOT
========================================================= */
function createRoot() {
if (
document.getElementById(
"study-os-host"
)
) {
return;
}
const host =
document.createElement(
"div"
);
host.id =
"study-os-host";
const shadow =
host.attachShadow({
mode: "open"
});
state.host =
host;
state.shadow =
shadow;
document.body.appendChild(
host
);
installStyles();
createLauncher();
console.log(
"[STUDY//OS] Launcher rendered."
);
}
/* =========================================================
LAUNCHER
========================================================= */
function createLauncher() {
const button =
create(
"button",
{
className:
"sos-launcher",
text:
"✦",
title:
"Open STUDY//OS",
type:
"button"
}
);
button.addEventListener(
"pointerdown",
function (event) {
event.preventDefault();
event.stopPropagation();
console.log(
"[STUDY//OS] Launcher clicked."
);
openPanel();
},
true
);
state.launcher =
button;
state.shadow.appendChild(
button
);
}
/* =========================================================
BOX CREATOR
========================================================= */
function createBox(title) {
const container =
create(
"div",
{
className:
"sos-box"
}
);
const titleElement =
create(
"div",
{
className:
"sos-box-title",
text:
title
}
);
const body =
create("div");
append(
container,
titleElement,
body
);
return {
container:
container,
body:
body
};
}
/* =========================================================
OPEN PANEL
========================================================= */
function openPanel() {
if (state.open) {
return;
}
state.open =
true;
if (state.launcher) {
state.launcher.remove();
}
const panel =
create(
"div",
{
className:
"sos-panel"
}
);
state.panel =
panel;
/* HEADER */
const header =
create(
"div",
{
className:
"sos-header"
}
);
const logo =
create("div");
const logoMain =
create(
"div",
{
className:
"sos-logo-main",
text:
"STUDY//OS"
}
);
const logoSub =
create(
"div",
{
className:
"sos-logo-sub",
text:
"STUDENT MULTITOOL // v" +
CONFIG.VERSION
}
);
append(
logo,
logoMain,
logoSub
);
const headerRight =
create(
"div",
{
className:
"sos-header-right"
}
);
const status =
create(
"div",
{
className:
"sos-status"
}
);
const dot =
create(
"span",
{
className:
"sos-status-dot"
}
);
const online =
create(
"span",
{
text:
"ONLINE"
}
);
append(
status,
dot,
online
);
const close =
create(
"button",
{
className:
"sos-close",
text:
"×",
type:
"button"
}
);
close.addEventListener(
"click",
closePanel
);
append(
headerRight,
status,
close
);
append(
header,
logo,
headerRight
);
/* CONTENT */
const content =
create(
"div",
{
className:
"sos-content"
}
);
/* MATERIAL INPUT */
const materialBox =
createBox(
"YOUR MATERIAL"
);
const inputWrap =
create(
"div",
{
className:
"sos-input-wrap"
}
);
const material =
create(
"textarea",
{
className:
"sos-material",
id:
"sos-material",
placeholder:
"Paste your essay, math problem, notes, question, assignment, or anything you want STUDY//OS to work with..."
}
);
const charCount =
create(
"div",
{
className:
"sos-char-count",
id:
"sos-char-count",
text:
"0 characters"
}
);
material.addEventListener(
"input",
function () {
updateCharacterCount();
}
);
append(
inputWrap,
material,
charCount
);
materialBox.body.appendChild(
inputWrap
);
/* TOOLS */
const toolsBox =
createBox(
"TOOLS"
);
const tools =
create(
"div",
{
className:
"sos-tools"
}
);
buildTools(
tools
);
toolsBox.body.appendChild(
tools
);
/* CUSTOM REQUEST */
const customBox =
create(
"div",
{
className:
"sos-box sos-custom",
id:
"sos-custom-box"
}
);
const customTitle =
create(
"div",
{
className:
"sos-box-title",
text:
"CUSTOM REQUEST"
}
);
const customWrap =
create("div");
const customInput =
create(
"textarea",
{
placeholder:
"What do you want STUDY//OS to do with the material?"
}
);
append(
customWrap,
customInput
);
append(
customBox,
customTitle,
customWrap
);
/* RUN */
const run =
create(
"button",
{
className:
"sos-run",
id:
"sos-run",
text:
"RUN STUDY//OS",
type:
"button"
}
);
run.addEventListener(
"click",
runAI
);
/* OUTPUT */
const outputBox =
createBox(
"OUTPUT"
);
const output =
create(
"div",
{
className:
"sos-output",
id:
"sos-output",
text:
"Your AI response will appear here."
}
);
outputBox.body.appendChild(
output
);
/* ACCESS */
const accessBox =
createBox(
"ACCESS"
);
const access =
create(
"div",
{
className:
"sos-access"
}
);
const accessStatus =
create(
"div",
{
className:
"sos-access-status",
id:
"sos-access-status"
}
);
const accessInput =
create(
"input",
{
className:
"sos-access-input",
id:
"sos-access-input",
type:
"password",
placeholder:
"STUDY-OS-XXXX-XXXX..."
}
);
accessInput.autocomplete =
"off";
const save =
create(
"button",
{
className:
"sos-save",
text:
"SAVE ACCESS KEY",
type:
"button"
}
);
save.addEventListener(
"click",
saveAccessKey
);
append(
access,
accessStatus,
accessInput,
save
);
accessBox.body.appendChild(
access
);
/* BUILD */
append(
content,
materialBox.container,
toolsBox.container,
customBox,
run,
outputBox.container,
accessBox.container
);
/* FOOTER */
const footer =
create(
"div",
{
className:
"sos-footer"
}
);
const version =
create(
"span",
{
text:
"STUDY//OS // " +
CONFIG.VERSION
}
);
const clear =
create(
"button",
{
text:
"CLEAR KEY",
type:
"button"
}
);
clear.addEventListener(
"click",
function () {
GM_deleteValue(
"study_os_access_key"
);
state.accessKey =
"";
updateAccessStatus();
toast(
"Access key cleared.",
"success"
);
}
);
append(
footer,
version,
clear
);
append(
panel,
header,
content,
footer
);
state.shadow.appendChild(
panel
);
updateAccessStatus();
updateCharacterCount();
console.log(
"[STUDY//OS] Panel opened."
);
}
/* =========================================================
TOOLS
========================================================= */
function buildTools(
container
) {
FEATURES.forEach(
feature => {
const button =
create(
"button",
{
className:
"sos-tool",
type:
"button"
}
);
const icon =
create(
"span",
{
className:
"sos-tool-icon",
text:
feature.icon
}
);
const title =
create(
"span",
{
text:
feature.title
}
);
const description =
create(
"span",
{
className:
"sos-tool-description",
text:
feature.description
}
);
append(
button,
icon,
title,
description
);
if (
feature.id ===
state.feature
) {
button.classList.add(
"active"
);
}
button.addEventListener(
"click",
function () {
state.feature =
feature.id;
container
.querySelectorAll(
".sos-tool"
)
.forEach(
item => {
item.classList
.remove(
"active"
);
}
);
button.classList.add(
"active"
);
const custom =
$("#sos-custom-box");
if (custom) {
custom.style.display =
feature.id ===
"custom"
? "block"
: "none";
}
}
);
container.appendChild(
button
);
}
);
}
/* =========================================================
CLOSE
========================================================= */
function closePanel() {
if (state.panel) {
state.panel.remove();
}
state.panel =
null;
state.open =
false;
createLauncher();
console.log(
"[STUDY//OS] Panel closed."
);
}
/* =========================================================
CHARACTER COUNT
========================================================= */
function updateCharacterCount() {
const input =
$("#sos-material");
const counter =
$("#sos-char-count");
if (!input || !counter) {
return;
}
const count =
input.value.length;
counter.textContent =
count.toLocaleString() +
" characters";
}
/* =========================================================
ACCESS KEY
========================================================= */
function saveAccessKey() {
const input =
$("#sos-access-input");
if (!input) {
return;
}
const key =
input.value.trim();
if (!key) {
toast(
"Enter your STUDY//OS access key.",
"error"
);
return;
}
state.accessKey =
key;
GM_setValue(
"study_os_access_key",
key
);
updateAccessStatus();
toast(
"Access key saved.",
"success"
);
}
function updateAccessStatus() {
const status =
$("#sos-access-status");
const input =
$("#sos-access-input");
if (!status) {
return;
}
if (state.accessKey) {
status.textContent =
"● ACCESS KEY SAVED";
status.style.color =
"#008833";
if (input) {
input.value =
state.accessKey;
}
} else {
status.textContent =
"○ NO ACCESS KEY";
status.style.color =
"#cc0000";
}
}
/* =========================================================
PROMPTS
========================================================= */
function buildPrompt(
feature,
material,
custom
) {
const base = `
You are STUDY//OS, an educational AI
assistant for high-school students.
Your job is to help students understand
their school material.
Be clear, accurate, concise, and educational.
Do not pretend to be the student.
Here is the student's material:
--- BEGIN MATERIAL ---
${material}
--- END MATERIAL ---
`;
switch (feature) {
case "explain":
return base + `
Explain this material in a way a
high-school student can understand.
Structure the response as:
MAIN IDEA
KEY CONCEPTS
SIMPLE EXPLANATION
EXAMPLE
WHAT TO REMEMBER
`;
case "writing":
return base + `
Act as a writing coach.
Analyze the writing for:
- grammar
- spelling
- clarity
- sentence structure
- organization
- repetition
- awkward wording
Explain the important problems.
Then provide an improved version while
preserving the student's general voice.
`;
case "math":
return base + `
Act as a math tutor.
Determine what the problem is asking.
Explain the relevant concept.
Work through the solution step-by-step.
Show calculations clearly.
Give the final answer separately.
`;
case "quiz":
return base + `
Create a five-question practice quiz.
Use a mixture of:
- multiple choice
- short answer
- application questions
Put the answer key at the bottom.
`;
case "summary":
return base + `
Turn this material into useful study notes.
Use:
MAIN IDEA
KEY TERMS
IMPORTANT FACTS
IMPORTANT DETAILS
QUICK REVIEW
`;
case "custom":
return base + `
Student request:
${custom}
Complete the request using the
provided material.
`;
default:
return base;
}
}
/* =========================================================
RUN
========================================================= */
async function runAI() {
if (state.loading) {
return;
}
const input =
$("#sos-material");
const material =
input
? input.value.trim()
: "";
if (!material) {
toast(
"Paste your material into the box first.",
"error"
);
if (input) {
input.focus();
}
return;
}
if (!state.accessKey) {
toast(
"Enter your STUDY//OS access key first.",
"error"
);
const accessInput =
$("#sos-access-input");
if (accessInput) {
accessInput.focus();
}
return;
}
let custom =
"";
if (
state.feature ===
"custom"
) {
const customBox =
$("#sos-custom-box");
const textarea =
customBox
? customBox.querySelector(
"textarea"
)
: null;
custom =
textarea
? textarea.value.trim()
: "";
if (!custom) {
toast(
"Tell ASK AI what you want it to do.",
"error"
);
return;
}
}
const prompt =
buildPrompt(
state.feature,
material,
custom
);
await requestAI(
prompt,
material
);
}
/* =========================================================
API REQUEST
========================================================= */
function requestAI(
prompt,
material
) {
return new Promise(
function (
resolve,
reject
) {
setLoading(
true
);
const endpoint =
CONFIG.SUPABASE_URL +
CONFIG.FUNCTION;
GM_xmlhttpRequest({
method:
"POST",
url:
endpoint,
timeout:
60000,
headers: {
"Content-Type":
"application/json",
"apikey":
CONFIG.SUPABASE_KEY,
"Authorization":
"Bearer " +
CONFIG.SUPABASE_KEY,
"X-STUDY-OS-KEY":
state.accessKey
},
data:
JSON.stringify({
model:
CONFIG.MODEL,
feature:
state.feature,
prompt:
prompt,
material:
material
}),
onload:
function (response) {
setLoading(
false
);
let data;
try {
data =
JSON.parse(
response.responseText
);
} catch {
data = {
error:
response.responseText
};
}
if (
response.status <
200 ||
response.status >=
300
) {
const message =
data.error ||
data.message ||
"Request failed: " +
response.status;
showOutput(
"ERROR\n\n" +
message
);
toast(
message,
"error"
);
reject(
new Error(
message
)
);
return;
}
const answer =
data.answer ||
data.response ||
data.content ||
data.message ||
(
data.data &&
(
data.data.answer ||
data.data.response ||
data.data.content
)
);
if (!answer) {
showOutput(
"The AI returned an empty response."
);
toast(
"Empty AI response.",
"error"
);
reject(
new Error(
"Empty response"
)
);
return;
}
showOutput(
String(answer)
);
toast(
"Response received.",
"success"
);
resolve(
answer
);
},
onerror:
function () {
setLoading(
false
);
showOutput(
"Unable to connect to STUDY//OS."
);
toast(
"Connection failed.",
"error"
);
reject(
new Error(
"Network error"
)
);
},
ontimeout:
function () {
setLoading(
false
);
showOutput(
"The request timed out."
);
toast(
"Request timed out.",
"error"
);
reject(
new Error(
"Timeout"
)
);
}
});
}
);
}
/* =========================================================
LOADING
========================================================= */
function setLoading(
loading
) {
state.loading =
loading;
const button =
$("#sos-run");
const output =
$("#sos-output");
if (!button) {
return;
}
if (loading) {
button.disabled =
true;
button.textContent =
"THINKING...";
if (output) {
output.textContent =
"STUDY//OS is thinking...";
}
} else {
button.disabled =
false;
button.textContent =
"RUN STUDY//OS";
}
}
/* =========================================================
OUTPUT
========================================================= */
function showOutput(
text
) {
const output =
$("#sos-output");
if (!output) {
return;
}
/*
* IMPORTANT:
*
* textContent is intentional.
*
* Never inject AI output as HTML.
*/
output.textContent =
String(
text || ""
);
}
/* =========================================================
TOAST
========================================================= */
function toast(
message,
type = ""
) {
if (!state.shadow) {
return;
}
const old =
state.shadow.querySelector(
".sos-toast"
);
if (old) {
old.remove();
}
const element =
create(
"div",
{
className:
"sos-toast " +
type,
text:
String(message)
}
);
state.shadow.appendChild(
element
);
setTimeout(
function () {
if (
element &&
element.isConnected
) {
element.remove();
}
},
3000
);
}
/* =========================================================
INIT
========================================================= */
function init() {
if (!document.body) {
setTimeout(
init,
300
);
return;
}
createRoot();
console.log(
"[STUDY//OS] v" +
CONFIG.VERSION +
" loaded."
);
}
init();
})();5 views