diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 0827091532..848bf4bb43 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -481,6 +481,8 @@ descendingOrder: "降順"
 scratchpad: "スクラッチパッド"
 scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。"
 output: "出力"
+script: "スクリプト"
+disablePagesScript: "Pagesのスクリプトを無効にする"
 
 _theme:
   explore: "テーマを探す"
@@ -813,6 +815,9 @@ _pages:
           message: "押したときに表示するメッセージ"
           variable: "送信する変数"
           no-variable: "なし"
+        callAiScript: "AiScript呼び出し"
+        _callAiScript:
+          functionName: "関数名"
 
     radioButton: "選択肢"
     _radioButton:
@@ -975,6 +980,7 @@ _pages:
       _splitStrByLine:
         arg1: "テキスト"
       ref: "変数"
+      aiScriptVar: "AiScript変数"
       fn: "関数"
       _fn:
         slots: "スロット"
diff --git a/migration/1586708940386-pageAiScript.ts b/migration/1586708940386-pageAiScript.ts
new file mode 100644
index 0000000000..fdd6e76b9b
--- /dev/null
+++ b/migration/1586708940386-pageAiScript.ts
@@ -0,0 +1,14 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class pageAiScript1586708940386 implements MigrationInterface {
+    name = 'pageAiScript1586708940386'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`ALTER TABLE "page" ADD "script" character varying(16384) NOT NULL DEFAULT ''`, undefined);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "script"`, undefined);
+    }
+
+}
diff --git a/package.json b/package.json
index 98ca48014c..8fa0129cb9 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
 		"@koa/cors": "3.0.0",
 		"@koa/multer": "2.0.2",
 		"@koa/router": "8.0.8",
-		"@syuilo/aiscript": "0.1.2",
+		"@syuilo/aiscript": "0.1.4",
 		"@types/bcryptjs": "2.4.2",
 		"@types/bull": "3.12.1",
 		"@types/cbor": "5.0.0",
diff --git a/src/client/components/page/page.button.vue b/src/client/components/page/page.button.vue
index eeb56d5eca..148fdc8e9c 100644
--- a/src/client/components/page/page.button.vue
+++ b/src/client/components/page/page.button.vue
@@ -28,7 +28,7 @@ export default Vue.extend({
 					text: this.script.interpolate(this.value.content)
 				});
 			} else if (this.value.action === 'resetRandom') {
-				this.script.aiScript.updateRandomSeed(Math.random());
+				this.script.aoiScript.updateRandomSeed(Math.random());
 				this.script.eval();
 			} else if (this.value.action === 'pushEvent') {
 				this.$root.api('page-push', {
@@ -43,6 +43,8 @@ export default Vue.extend({
 					type: 'success',
 					text: this.script.interpolate(this.value.message)
 				});
+			} else if (this.value.action === 'callAiScript') {
+				this.script.callAiScript(this.value.fn);
 			}
 		}
 	}
diff --git a/src/client/components/page/page.counter.vue b/src/client/components/page/page.counter.vue
index 781a1bd549..f7557c003a 100644
--- a/src/client/components/page/page.counter.vue
+++ b/src/client/components/page/page.counter.vue
@@ -27,7 +27,7 @@ export default Vue.extend({
 	},
 	watch: {
 		v() {
-			this.script.aiScript.updatePageVar(this.value.name, this.v);
+			this.script.aoiScript.updatePageVar(this.value.name, this.v);
 			this.script.eval();
 		}
 	},
diff --git a/src/client/components/page/page.number-input.vue b/src/client/components/page/page.number-input.vue
index 9ee2730fac..9ea1ebb642 100644
--- a/src/client/components/page/page.number-input.vue
+++ b/src/client/components/page/page.number-input.vue
@@ -27,7 +27,7 @@ export default Vue.extend({
 	},
 	watch: {
 		v() {
-			this.script.aiScript.updatePageVar(this.value.name, this.v);
+			this.script.aoiScript.updatePageVar(this.value.name, this.v);
 			this.script.eval();
 		}
 	}
diff --git a/src/client/components/page/page.radio-button.vue b/src/client/components/page/page.radio-button.vue
index fda0a03927..dd5cbcbded 100644
--- a/src/client/components/page/page.radio-button.vue
+++ b/src/client/components/page/page.radio-button.vue
@@ -28,7 +28,7 @@ export default Vue.extend({
 	},
 	watch: {
 		v() {
-			this.script.aiScript.updatePageVar(this.value.name, this.v);
+			this.script.aoiScript.updatePageVar(this.value.name, this.v);
 			this.script.eval();
 		}
 	}
diff --git a/src/client/components/page/page.switch.vue b/src/client/components/page/page.switch.vue
index 416c36e9ad..79d871df8f 100644
--- a/src/client/components/page/page.switch.vue
+++ b/src/client/components/page/page.switch.vue
@@ -27,7 +27,7 @@ export default Vue.extend({
 	},
 	watch: {
 		v() {
-			this.script.aiScript.updatePageVar(this.value.name, this.v);
+			this.script.aoiScript.updatePageVar(this.value.name, this.v);
 			this.script.eval();
 		}
 	}
diff --git a/src/client/components/page/page.text-input.vue b/src/client/components/page/page.text-input.vue
index fcc181d673..843d541de6 100644
--- a/src/client/components/page/page.text-input.vue
+++ b/src/client/components/page/page.text-input.vue
@@ -27,7 +27,7 @@ export default Vue.extend({
 	},
 	watch: {
 		v() {
-			this.script.aiScript.updatePageVar(this.value.name, this.v);
+			this.script.aoiScript.updatePageVar(this.value.name, this.v);
 			this.script.eval();
 		}
 	}
diff --git a/src/client/components/page/page.textarea-input.vue b/src/client/components/page/page.textarea-input.vue
index d1cf9813c4..5ba22e7c58 100644
--- a/src/client/components/page/page.textarea-input.vue
+++ b/src/client/components/page/page.textarea-input.vue
@@ -27,7 +27,7 @@ export default Vue.extend({
 	},
 	watch: {
 		v() {
-			this.script.aiScript.updatePageVar(this.value.name, this.v);
+			this.script.aoiScript.updatePageVar(this.value.name, this.v);
 			this.script.eval();
 		}
 	}
diff --git a/src/client/components/page/page.vue b/src/client/components/page/page.vue
index 977d384b30..0f1769fc8f 100644
--- a/src/client/components/page/page.vue
+++ b/src/client/components/page/page.vue
@@ -6,30 +6,57 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import i18n from '../../i18n';
+import { AiScript, parse, values } from '@syuilo/aiscript';
 import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
 import { faHeart } from '@fortawesome/free-regular-svg-icons';
+import i18n from '../../i18n';
 import XBlock from './page.block.vue';
 import { ASEvaluator } from '../../scripts/aoiscript/evaluator';
 import { collectPageVars } from '../../scripts/collect-page-vars';
 import { url } from '../../config';
 
 class Script {
-	public aiScript: ASEvaluator;
+	public aoiScript: ASEvaluator;
 	private onError: any;
 	public vars: Record<string, any>;
 	public page: Record<string, any>;
 
-	constructor(page, aiScript, onError) {
+	constructor(page, aoiScript, onError, cb) {
 		this.page = page;
-		this.aiScript = aiScript;
+		this.aoiScript = aoiScript;
 		this.onError = onError;
-		this.eval();
+
+		if (this.page.script) {
+			let ast;
+			try {
+				ast = parse(this.page.script);
+			} catch (e) {
+				console.error(e);
+				/*this.$root.dialog({
+					type: 'error',
+					text: 'Syntax error :('
+				});*/
+				return;
+			}
+			this.aoiScript.aiscript.exec(ast).then(() => {
+				this.eval();
+				cb();
+			}).catch(e => {
+				console.error(e);
+				/*this.$root.dialog({
+					type: 'error',
+					text: e
+				});*/
+			});
+		} else {
+			this.eval();
+			cb();
+		}
 	}
 
 	public eval() {
 		try {
-			this.vars = this.aiScript.evaluateVars();
+			this.vars = this.aoiScript.evaluateVars();
 		} catch (e) {
 			this.onError(e);
 		}
@@ -42,6 +69,10 @@ class Script {
 			return v == null ? 'NULL' : v.toString();
 		});
 	}
+
+	public callAiScript(fn: string) {
+		this.aoiScript.aiscript.execFn(this.aoiScript.aiscript.scope.get(fn), []);
+	}
 }
 
 export default Vue.extend({
@@ -67,14 +98,21 @@ export default Vue.extend({
 
 	created() {
 		const pageVars = this.getPageVars();
-		this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, {
+		
+		const s = new Script(this.page, new ASEvaluator(this, this.page.variables, pageVars, {
 			randomSeed: Math.random(),
 			visitor: this.$store.state.i,
 			page: this.page,
 			url: url
 		}), e => {
 			console.dir(e);
+		}, () => {
+			this.script = s;
 		});
+
+		s.aoiScript.aiscript.scope.opts.onUpdated = (name, value) => {
+			s.eval();
+		};
 	},
 
 	methods: {
diff --git a/src/client/pages/page-editor/els/page-editor.el.button.vue b/src/client/pages/page-editor/els/page-editor.el.button.vue
index 8e74124b79..508d77a7a0 100644
--- a/src/client/pages/page-editor/els/page-editor.el.button.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.button.vue
@@ -10,6 +10,7 @@
 			<option value="dialog">{{ $t('_pages.blocks._button._action.dialog') }}</option>
 			<option value="resetRandom">{{ $t('_pages.blocks._button._action.resetRandom') }}</option>
 			<option value="pushEvent">{{ $t('_pages.blocks._button._action.pushEvent') }}</option>
+			<option value="callAiScript">{{ $t('_pages.blocks._button._action.callAiScript') }}</option>
 		</mk-select>
 		<template v-if="value.action === 'dialog'">
 			<mk-input v-model="value.content"><span>{{ $t('_pages.blocks._button._action._dialog.content') }}</span></mk-input>
@@ -20,15 +21,18 @@
 			<mk-select v-model="value.var">
 				<template #label>{{ $t('_pages.blocks._button._action._pushEvent.variable') }}</template>
 				<option :value="null">{{ $t('_pages.blocks._button._action._pushEvent.no-variable') }}</option>
-				<option v-for="v in aiScript.getVarsByType()" :value="v.name">{{ v.name }}</option>
+				<option v-for="v in aoiScript.getVarsByType()" :value="v.name">{{ v.name }}</option>
 				<optgroup :label="$t('_pages.script.pageVariables')">
-					<option v-for="v in aiScript.getPageVarsByType()" :value="v">{{ v }}</option>
+					<option v-for="v in aoiScript.getPageVarsByType()" :value="v">{{ v }}</option>
 				</optgroup>
 				<optgroup :label="$t('_pages.script.enviromentVariables')">
-					<option v-for="v in aiScript.getEnvVarsByType()" :value="v">{{ v }}</option>
+					<option v-for="v in aoiScript.getEnvVarsByType()" :value="v">{{ v }}</option>
 				</optgroup>
 			</mk-select>
 		</template>
+		<template v-else-if="value.action === 'callAiScript'">
+			<mk-input v-model="value.fn"><span>{{ $t('_pages.blocks._button._action._callAiScript.functionName') }}</span></mk-input>
+		</template>
 	</section>
 </x-container>
 </template>
@@ -53,7 +57,7 @@ export default Vue.extend({
 		value: {
 			required: true
 		},
-		aiScript: {
+		aoiScript: {
 			required: true,
 		},
 	},
@@ -72,6 +76,7 @@ export default Vue.extend({
 		if (this.value.message == null) Vue.set(this.value, 'message', null);
 		if (this.value.primary == null) Vue.set(this.value, 'primary', false);
 		if (this.value.var == null) Vue.set(this.value, 'var', null);
+		if (this.value.fn == null) Vue.set(this.value, 'fn', null);
 	},
 });
 </script>
diff --git a/src/client/pages/page-editor/els/page-editor.el.if.vue b/src/client/pages/page-editor/els/page-editor.el.if.vue
index 5e531a7ab5..0c40e41d8a 100644
--- a/src/client/pages/page-editor/els/page-editor.el.if.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.if.vue
@@ -10,16 +10,16 @@
 	<section class="romcojzs">
 		<mk-select v-model="value.var">
 			<template #label>{{ $t('_pages.blocks._if.variable') }}</template>
-			<option v-for="v in aiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
+			<option v-for="v in aoiScript.getVarsByType('boolean')" :value="v.name">{{ v.name }}</option>
 			<optgroup :label="$t('_pages.script.pageVariables')">
-				<option v-for="v in aiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
+				<option v-for="v in aoiScript.getPageVarsByType('boolean')" :value="v">{{ v }}</option>
 			</optgroup>
 			<optgroup :label="$t('_pages.script.enviromentVariables')">
-				<option v-for="v in aiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
+				<option v-for="v in aoiScript.getEnvVarsByType('boolean')" :value="v">{{ v }}</option>
 			</optgroup>
 		</mk-select>
 
-		<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/>
+		<x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/>
 	</section>
 </x-container>
 </template>
@@ -45,7 +45,7 @@ export default Vue.extend({
 		value: {
 			required: true
 		},
-		aiScript: {
+		aoiScript: {
 			required: true,
 		},
 	},
diff --git a/src/client/pages/page-editor/els/page-editor.el.section.vue b/src/client/pages/page-editor/els/page-editor.el.section.vue
index 8de796e6d6..97f063a287 100644
--- a/src/client/pages/page-editor/els/page-editor.el.section.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.section.vue
@@ -11,7 +11,7 @@
 	</template>
 
 	<section class="ilrvjyvi">
-		<x-blocks class="children" v-model="value.children" :ai-script="aiScript"/>
+		<x-blocks class="children" v-model="value.children" :aoi-script="aoiScript"/>
 	</section>
 </x-container>
 </template>
@@ -37,7 +37,7 @@ export default Vue.extend({
 		value: {
 			required: true
 		},
-		aiScript: {
+		aoiScript: {
 			required: true,
 		},
 	},
diff --git a/src/client/pages/page-editor/els/page-editor.el.text.vue b/src/client/pages/page-editor/els/page-editor.el.text.vue
index 00b6cd8a36..c6722236eb 100644
--- a/src/client/pages/page-editor/els/page-editor.el.text.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.text.vue
@@ -2,7 +2,7 @@
 <x-container @remove="() => $emit('remove')" :draggable="true">
 	<template #header><fa :icon="faAlignLeft"/> {{ $t('_pages.blocks.text') }}</template>
 
-	<section class="ihymsbbe">
+	<section class="vckmsadr">
 		<textarea v-model="value.text"></textarea>
 	</section>
 </x-container>
@@ -40,7 +40,7 @@ export default Vue.extend({
 </script>
 
 <style lang="scss" scoped>
-.ihymsbbe {
+.vckmsadr {
 	> textarea {
 		display: block;
 		-webkit-appearance: none;
@@ -55,6 +55,7 @@ export default Vue.extend({
 		background: transparent;
 		color: var(--fg);
 		font-size: 14px;
+		box-sizing: border-box;
 	}
 }
 </style>
diff --git a/src/client/pages/page-editor/els/page-editor.el.textarea.vue b/src/client/pages/page-editor/els/page-editor.el.textarea.vue
index fd75849684..d31da5dfa3 100644
--- a/src/client/pages/page-editor/els/page-editor.el.textarea.vue
+++ b/src/client/pages/page-editor/els/page-editor.el.textarea.vue
@@ -55,6 +55,7 @@ export default Vue.extend({
 		background: transparent;
 		color: var(--fg);
 		font-size: 14px;
+		box-sizing: border-box;
 	}
 }
 </style>
diff --git a/src/client/pages/page-editor/page-editor.blocks.vue b/src/client/pages/page-editor/page-editor.blocks.vue
index 4d7293231f..bfc75cada4 100644
--- a/src/client/pages/page-editor/page-editor.blocks.vue
+++ b/src/client/pages/page-editor/page-editor.blocks.vue
@@ -1,6 +1,6 @@
 <template>
 <x-draggable tag="div" :list="blocks" handle=".drag-handle" :group="{ name: 'blocks' }" animation="150" swap-threshold="0.5">
-	<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :ai-script="aiScript"/>
+	<component v-for="block in blocks" :is="'x-' + block.type" :value="block" @input="updateItem" @remove="() => removeItem(block)" :key="block.id" :aoi-script="aoiScript"/>
 </x-draggable>
 </template>
 
@@ -31,7 +31,7 @@ export default Vue.extend({
 			type: Array,
 			required: true
 		},
-		aiScript: {
+		aoiScript: {
 			required: true,
 		},
 	},
diff --git a/src/client/pages/page-editor/page-editor.script-block.vue b/src/client/pages/page-editor/page-editor.script-block.vue
index 4f30b7136b..7e3bbf0c88 100644
--- a/src/client/pages/page-editor/page-editor.script-block.vue
+++ b/src/client/pages/page-editor/page-editor.script-block.vue
@@ -2,7 +2,7 @@
 <x-container :removable="removable" @remove="() => $emit('remove')" :error="error" :warn="warn" :draggable="draggable">
 	<template #header><fa v-if="icon" :icon="icon"/> <template v-if="title">{{ title }} <span class="turmquns" v-if="typeText">({{ typeText }})</span></template><template v-else-if="typeText">{{ typeText }}</template></template>
 	<template #func>
-		<button @click="changeType()">
+		<button @click="changeType()" class="_button">
 			<fa :icon="faPencilAlt"/>
 		</button>
 	</template>
@@ -24,30 +24,33 @@
 	</section>
 	<section v-else-if="value.type === 'ref'" class="hpdwcrvs">
 		<select v-model="value.value">
-			<option v-for="v in aiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
+			<option v-for="v in aoiScript.getVarsByType(getExpectedType ? getExpectedType() : null).filter(x => x.name !== name)" :value="v.name">{{ v.name }}</option>
 			<optgroup :label="$t('_pages.script.argVariables')">
 				<option v-for="v in fnSlots" :value="v.name">{{ v.name }}</option>
 			</optgroup>
 			<optgroup :label="$t('_pages.script.pageVariables')">
-				<option v-for="v in aiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
+				<option v-for="v in aoiScript.getPageVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
 			</optgroup>
 			<optgroup :label="$t('_pages.script.enviromentVariables')">
-				<option v-for="v in aiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
+				<option v-for="v in aoiScript.getEnvVarsByType(getExpectedType ? getExpectedType() : null)" :value="v">{{ v }}</option>
 			</optgroup>
 		</select>
 	</section>
+	<section v-else-if="value.type === 'aiScriptVar'" class="tbwccoaw">
+		<input v-model="value.value"/>
+	</section>
 	<section v-else-if="value.type === 'fn'" class="" style="padding:0 16px 16px 16px;">
 		<mk-textarea v-model="slots">
 			<span>{{ $t('_pages.script.blocks._fn.slots') }}</span>
 			<template #desc>{{ $t('_pages.script.blocks._fn.slots-info') }}</template>
 		</mk-textarea>
-		<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :ai-script="aiScript" :fn-slots="value.value.slots" :name="name"/>
+		<x-v v-if="value.value.expression" v-model="value.value.expression" :title="$t(`_pages.script.blocks._fn.arg1`)" :get-expected-type="() => null" :aoi-script="aoiScript" :fn-slots="value.value.slots" :name="name"/>
 	</section>
 	<section v-else-if="value.type.startsWith('fn:')" class="" style="padding:16px;">
-		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :ai-script="aiScript" :name="name" :key="i"/>
+		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="aoiScript.getVarByName(value.type.split(':')[1]).value.slots[i].name" :get-expected-type="() => null" :aoi-script="aoiScript" :name="name" :key="i"/>
 	</section>
 	<section v-else class="" style="padding:16px;">
-		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :ai-script="aiScript" :name="name" :fn-slots="fnSlots" :key="i"/>
+		<x-v v-for="(x, i) in value.args" v-model="value.args[i]" :title="$t(`_pages.script.blocks._${value.type}.arg${i + 1}`)" :get-expected-type="() => _getExpectedType(i)" :aoi-script="aoiScript" :name="name" :fn-slots="fnSlots" :key="i"/>
 	</section>
 </x-container>
 </template>
@@ -85,7 +88,7 @@ export default Vue.extend({
 			required: false,
 			default: false
 		},
-		aiScript: {
+		aoiScript: {
 			required: true,
 		},
 		name: {
@@ -153,7 +156,7 @@ export default Vue.extend({
 
 			if (this.value.type && this.value.type.startsWith('fn:')) {
 				const fnName = this.value.type.split(':')[1];
-				const fn = this.aiScript.getVarByName(fnName);
+				const fn = this.aoiScript.getVarByName(fnName);
 
 				const empties = [];
 				for (let i = 0; i < fn.value.slots.length; i++) {
@@ -199,9 +202,9 @@ export default Vue.extend({
 			deep: true
 		});
 
-		this.$watch('aiScript.variables', () => {
+		this.$watch('aoiScript.variables', () => {
 			if (this.type != null && this.value) {
-				this.error = this.aiScript.typeCheck(this.value);
+				this.error = this.aoiScript.typeCheck(this.value);
 			}
 		}, {
 			deep: true
@@ -223,7 +226,7 @@ export default Vue.extend({
 		},
 
 		_getExpectedType(slot: number) {
-			return this.aiScript.getExpectedType(this.value, slot);
+			return this.aoiScript.getExpectedType(this.value, slot);
 		}
 	}
 });
@@ -258,6 +261,7 @@ export default Vue.extend({
 		font-size: 16px;
 		background: transparent;
 		color: var(--fg);
+		box-sizing: border-box;
 	}
 
 	> textarea {
diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue
index b3350e0c63..6177663b71 100644
--- a/src/client/pages/page-editor/page-editor.vue
+++ b/src/client/pages/page-editor/page-editor.vue
@@ -46,7 +46,7 @@
 				</div>
 			</template>
 
-			<x-blocks class="content" v-model="content" :ai-script="aiScript"/>
+			<x-blocks class="content" v-model="content" :aoi-script="aoiScript"/>
 
 			<mk-button @click="add()" v-if="!readonly"><fa :icon="faPlus"/></mk-button>
 		</section>
@@ -62,7 +62,7 @@
 					@input="v => updateVariable(v)"
 					@remove="() => removeVariable(variable)"
 					:key="variable.name"
-					:ai-script="aiScript"
+					:aoi-script="aoiScript"
 					:name="variable.name"
 					:title="variable.name"
 					:draggable="true"
@@ -73,11 +73,10 @@
 		</div>
 	</mk-container>
 
-	<mk-container :body-togglable="true" :expanded="false">
-		<template #header><fa :icon="faCode"/> {{ $t('_pages.inspector') }}</template>
-		<div style="padding:0 32px 32px 32px;">
-			<mk-textarea :value="JSON.stringify(content, null, 2)" readonly tall>{{ $t('_pages.content') }}</mk-textarea>
-			<mk-textarea :value="JSON.stringify(variables, null, 2)" readonly tall>{{ $t('_pages.variables') }}</mk-textarea>
+	<mk-container :body-togglable="true" :expanded="true">
+		<template #header><fa :icon="faCode"/> {{ $t('script') }}</template>
+		<div>
+			<prism-editor v-model="script" :line-numbers="false" language="js"/>
 		</div>
 	</mk-container>
 </div>
@@ -86,6 +85,9 @@
 <script lang="ts">
 import Vue from 'vue';
 import * as XDraggable from 'vuedraggable';
+import "prismjs";
+import "prismjs/themes/prism.css";
+import PrismEditor from 'vue-prism-editor';
 import { faICursor, faPlus, faMagic, faCog, faCode, faExternalLinkSquareAlt } from '@fortawesome/free-solid-svg-icons';
 import { faSave, faStickyNote, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 import { v4 as uuid } from 'uuid';
@@ -108,7 +110,7 @@ export default Vue.extend({
 	i18n,
 
 	components: {
-		XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput
+		XDraggable, XVariable, XBlocks, MkTextarea, MkContainer, MkButton, MkSelect, MkSwitch, MkInput, PrismEditor
 	},
 
 	props: {
@@ -143,7 +145,8 @@ export default Vue.extend({
 			alignCenter: false,
 			hideTitleWhenPinned: false,
 			variables: [],
-			aiScript: null,
+			aoiScript: null,
+			script: '',
 			showOptions: false,
 			url,
 			faPlus, faICursor, faSave, faStickyNote, faMagic, faCog, faTrashAlt, faExternalLinkSquareAlt, faCode
@@ -163,14 +166,14 @@ export default Vue.extend({
 	},
 
 	async created() {
-		this.aiScript = new ASTypeChecker();
+		this.aoiScript = new ASTypeChecker();
 
 		this.$watch('variables', () => {
-			this.aiScript.variables = this.variables;
+			this.aoiScript.variables = this.variables;
 		}, { deep: true });
 
 		this.$watch('content', () => {
-			this.aiScript.pageVars = collectPageVars(this.content);
+			this.aoiScript.pageVars = collectPageVars(this.content);
 		}, { deep: true });
 
 		if (this.initPageId) {
@@ -193,6 +196,7 @@ export default Vue.extend({
 			this.currentName = this.page.name;
 			this.summary = this.page.summary;
 			this.font = this.page.font;
+			this.script = this.page.script;
 			this.hideTitleWhenPinned = this.page.hideTitleWhenPinned;
 			this.alignCenter = this.page.alignCenter;
 			this.content = this.page.content;
@@ -223,6 +227,7 @@ export default Vue.extend({
 				name: this.name.trim(),
 				summary: this.summary,
 				font: this.font,
+				script: this.script,
 				hideTitleWhenPinned: this.hideTitleWhenPinned,
 				alignCenter: this.alignCenter,
 				content: this.content,
@@ -317,7 +322,7 @@ export default Vue.extend({
 
 			name = name.trim();
 
-			if (this.aiScript.isUsedName(name)) {
+			if (this.aoiScript.isUsedName(name)) {
 				this.$root.dialog({
 					type: 'error',
 					text: this.$t('_pages.variableNameIsAlreadyUsed')
@@ -382,7 +387,7 @@ export default Vue.extend({
 				} else {
 					list.push({
 						category: block.category,
-						label: this.$t(`script.categories.${block.category}`),
+						label: this.$t(`_pages.script.categories.${block.category}`),
 						items: [{
 							value: block.type,
 							text: this.$t(`_pages.script.blocks.${block.type}`)
@@ -394,7 +399,7 @@ export default Vue.extend({
 			const userFns = this.variables.filter(x => x.type === 'fn');
 			if (userFns.length > 0) {
 				list.unshift({
-					label: this.$t(`script.categories.fn`),
+					label: this.$t(`_pages.script.categories.fn`),
 					items: userFns.map(v => ({
 						value: 'fn:' + v.name,
 						text: v.name
diff --git a/src/client/pages/scratchpad.vue b/src/client/pages/scratchpad.vue
index 63ce5d99d7..e48beababa 100644
--- a/src/client/pages/scratchpad.vue
+++ b/src/client/pages/scratchpad.vue
@@ -23,9 +23,9 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import { faTerminal, faPlay } from '@fortawesome/free-solid-svg-icons';
 import "prismjs";
 import "prismjs/themes/prism.css";
-import { faTerminal, faPlay } from '@fortawesome/free-solid-svg-icons';
 import PrismEditor from 'vue-prism-editor';
 import { AiScript, parse, utils, values } from '@syuilo/aiscript';
 import i18n from '../i18n';
diff --git a/src/client/scripts/aoiscript/evaluator.ts b/src/client/scripts/aoiscript/evaluator.ts
index 2e952da404..8d8a5b2e08 100644
--- a/src/client/scripts/aoiscript/evaluator.ts
+++ b/src/client/scripts/aoiscript/evaluator.ts
@@ -2,6 +2,8 @@ import autobind from 'autobind-decorator';
 import * as seedrandom from 'seedrandom';
 import { Variable, PageVar, envVarsDef, funcDefs, Block, isFnBlock } from '.';
 import { version } from '../../config';
+import { AiScript, utils, parse, values } from '@syuilo/aiscript';
+import { createAiScriptEnv } from '../create-aiscript-env';
 
 type Fn = {
 	slots: string[];
@@ -15,15 +17,41 @@ export class ASEvaluator {
 	private variables: Variable[];
 	private pageVars: PageVar[];
 	private envVars: Record<keyof typeof envVarsDef, any>;
+	public aiscript: AiScript;
+	private pageVarUpdatedCallback;
 
 	private opts: {
 		randomSeed: string; visitor?: any; page?: any; url?: string;
 	};
 
-	constructor(variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) {
+	constructor(vm: any, variables: Variable[], pageVars: PageVar[], opts: ASEvaluator['opts']) {
 		this.variables = variables;
 		this.pageVars = pageVars;
 		this.opts = opts;
+		this.aiscript = new AiScript({ ...createAiScriptEnv(vm, {
+			storageKey: 'pages:' + opts.page.id
+		}), ...{
+			'MkPages:updated': values.FN_NATIVE(([callback]) => {
+				this.pageVarUpdatedCallback = callback;
+			})
+		}}, {
+			in: (q) => {
+				return new Promise(ok => {
+					vm.$root.dialog({
+						title: q,
+						input: {}
+					}).then(({ canceled, result: a }) => {
+						ok(a);
+					});
+				});
+			},
+			out: (value) => {
+				console.log(value);
+			},
+			log: (type, params) => {
+			},
+			maxStep: 16384
+		});
 
 		const date = new Date();
 
@@ -50,6 +78,9 @@ export class ASEvaluator {
 		const pageVar = this.pageVars.find(v => v.name === name);
 		if (pageVar !== undefined) {
 			pageVar.value = value;
+			if (this.pageVarUpdatedCallback) {
+				this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]);
+			}
 		} else {
 			throw new AoiScriptError(`No such page var '${name}'`);
 		}
@@ -110,6 +141,10 @@ export class ASEvaluator {
 			return scope.getState(block.value);
 		}
 
+		if (block.type === 'aiScriptVar') {
+			return utils.valToJs(this.aiscript.scope.get(block.value));
+		}
+
 		if (isFnBlock(block)) { // ユーザー関数定義
 			return {
 				slots: block.value.slots.map(x => x.name),
diff --git a/src/client/scripts/aoiscript/index.ts b/src/client/scripts/aoiscript/index.ts
index 42d67b3fad..e6de5faaae 100644
--- a/src/client/scripts/aoiscript/index.ts
+++ b/src/client/scripts/aoiscript/index.ts
@@ -95,6 +95,7 @@ export const literalDefs: Record<string, { out: any; category: string; icon: any
 	textList:      { out: 'stringArray', category: 'value', icon: faList, },
 	number:        { out: 'number',      category: 'value', icon: faSortNumericUp, },
 	ref:           { out: null,          category: 'value', icon: faMagic, },
+	aiScriptVar:   { out: null,          category: 'value', icon: faMagic, },
 	fn:            { out: 'function',    category: 'value', icon: faSquareRootAlt, },
 };
 
diff --git a/src/client/scripts/create-aiscript-env.ts b/src/client/scripts/create-aiscript-env.ts
index 3f7b0c80b5..5a492c64dd 100644
--- a/src/client/scripts/create-aiscript-env.ts
+++ b/src/client/scripts/create-aiscript-env.ts
@@ -1,6 +1,7 @@
 import { utils, values } from '@syuilo/aiscript';
 
 export function createAiScriptEnv(vm, opts) {
+	let apiRequests = 0;
 	return {
 		USER_ID: values.STR(vm.$store.state.i.id),
 		USER_USERNAME: values.STR(vm.$store.state.i.username),
@@ -21,6 +22,8 @@ export function createAiScriptEnv(vm, opts) {
 			return confirm.canceled ? values.FALSE : values.TRUE
 		}),
 		'Mk:api': values.FN_NATIVE(async ([ep, param, token]) => {
+			apiRequests++;
+			if (apiRequests > 16) return values.NULL;
 			const res = await vm.$root.api(ep.value, utils.valToJs(param), token || null);
 			return utils.jsToVal(res);
 		}),
diff --git a/src/models/entities/page.ts b/src/models/entities/page.ts
index 2163f9997f..ed0411a3d0 100644
--- a/src/models/entities/page.ts
+++ b/src/models/entities/page.ts
@@ -85,6 +85,12 @@ export class Page {
 	})
 	public variables: Record<string, any>[];
 
+	@Column('varchar', {
+		length: 16384,
+		default: ''
+	})
+	public script: string;
+
 	/**
 	 * public ... 公開
 	 * followers ... フォロワーのみ
diff --git a/src/models/repositories/page.ts b/src/models/repositories/page.ts
index cff42ddefd..662c41905f 100644
--- a/src/models/repositories/page.ts
+++ b/src/models/repositories/page.ts
@@ -74,6 +74,7 @@ export class PageRepository extends Repository<Page> {
 			hideTitleWhenPinned: page.hideTitleWhenPinned,
 			alignCenter: page.alignCenter,
 			font: page.font,
+			script: page.script,
 			eyeCatchingImageId: page.eyeCatchingImageId,
 			eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
 			attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
diff --git a/src/server/api/endpoints/pages/create.ts b/src/server/api/endpoints/pages/create.ts
index 11e476242e..6d41a4afeb 100644
--- a/src/server/api/endpoints/pages/create.ts
+++ b/src/server/api/endpoints/pages/create.ts
@@ -44,6 +44,10 @@ export const meta = {
 			validator: $.arr($.obj())
 		},
 
+		script: {
+			validator: $.str,
+		},
+
 		eyeCatchingImageId: {
 			validator: $.optional.nullable.type(ID),
 		},
@@ -115,6 +119,7 @@ export default define(meta, async (ps, user) => {
 		summary: ps.summary,
 		content: ps.content,
 		variables: ps.variables,
+		script: ps.script,
 		eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
 		userId: user.id,
 		visibility: 'public',
diff --git a/src/server/api/endpoints/pages/update.ts b/src/server/api/endpoints/pages/update.ts
index a0fed28891..2d93dd4ae4 100644
--- a/src/server/api/endpoints/pages/update.ts
+++ b/src/server/api/endpoints/pages/update.ts
@@ -51,6 +51,10 @@ export const meta = {
 			validator: $.arr($.obj())
 		},
 
+		script: {
+			validator: $.str,
+		},
+
 		eyeCatchingImageId: {
 			validator: $.optional.nullable.type(ID),
 		},
@@ -132,6 +136,7 @@ export default define(meta, async (ps, user) => {
 		summary: ps.name === undefined ? page.summary : ps.summary,
 		content: ps.content,
 		variables: ps.variables,
+		script: ps.script,
 		alignCenter: ps.alignCenter === undefined ? page.alignCenter : ps.alignCenter,
 		hideTitleWhenPinned: ps.hideTitleWhenPinned === undefined ? page.hideTitleWhenPinned : ps.hideTitleWhenPinned,
 		font: ps.font === undefined ? page.font : ps.font,
diff --git a/yarn.lock b/yarn.lock
index 2d79d7d947..598ea8ee5a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -144,10 +144,10 @@
   dependencies:
     type-detect "4.0.8"
 
-"@syuilo/aiscript@0.1.2":
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.1.2.tgz#65c42793c38707d862b3a64f5edc845789372ade"
-  integrity sha512-W0G/JuVkD9jARPhKFaaHp+59Iv+2LapQ2zKjM08hoB/6hEzHjis0uRbw07TXyughQb17iU452rp1gJEUkXV3Mg==
+"@syuilo/aiscript@0.1.4":
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/@syuilo/aiscript/-/aiscript-0.1.4.tgz#ff027552f32990ae3e29145ce6efe0a7a516b442"
+  integrity sha512-SMDlBInsGTL3DOe0U394X7na0N6ryYg0RGQPPtCVhXkJpVDZiaqUe5vDO+DkRyuRlkmBbN82LWToou19j/Uv8g==
   dependencies:
     autobind-decorator "2.4.0"
     chalk "4.0.0"