diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 2233fa27f15f0b9f8049d3f79747b2ed280323ca..3f8542dfd0c240595599829e0bef6e9568620904 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1067,6 +1067,7 @@ _widgets:
   onlineUsers: "オンラインユーザー"
   jobQueue: "ジョブキュー"
   serverMetric: "サーバーメトリクス"
+  aiscript: "AiScriptコンソール"
 
 _cw:
   hide: "隠す"
diff --git a/src/client/widgets/aiscript.vue b/src/client/widgets/aiscript.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4e788b4b4a9fcfcb03bf041676e93bc5b48d7feb
--- /dev/null
+++ b/src/client/widgets/aiscript.vue
@@ -0,0 +1,164 @@
+<template>
+<MkContainer :show-header="props.showHeader">
+	<template #header><Fa :icon="faTerminal"/>{{ $ts._widgets.aiscript }}</template>
+
+	<div class="uylguesu _monospace">
+		<textarea v-model="props.script" placeholder="(1 + 1)"></textarea>
+		<button @click="run" class="_buttonPrimary">RUN</button>
+		<div class="logs">
+			<div v-for="log in logs" class="log" :key="log.id" :class="{ print: log.print }">{{ log.text }}</div>
+		</div>
+	</div>
+</MkContainer>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { faTerminal } from '@fortawesome/free-solid-svg-icons';
+import MkContainer from '@/components/ui/container.vue';
+import define from './define';
+import * as os from '@/os';
+import { AiScript, parse, utils } from '@syuilo/aiscript';
+import { createAiScriptEnv } from '@/scripts/aiscript/api';
+
+const widget = define({
+	name: 'aiscript',
+	props: () => ({
+		showHeader: {
+			type: 'boolean',
+			default: true,
+		},
+		script: {
+			type: 'string',
+			multiline: true,
+			default: '(1 + 1)',
+			hidden: true,
+		},
+	})
+});
+
+export default defineComponent({
+	extends: widget,
+	components: {
+		MkContainer
+	},
+
+	data() {
+		return {
+			logs: [],
+			faTerminal
+		};
+	},
+
+	methods: {
+		async run() {
+			this.logs = [];
+			const aiscript = new AiScript(createAiScriptEnv({
+				storageKey: 'widget'
+			}), {
+				in: (q) => {
+					return new Promise(ok => {
+						os.dialog({
+							title: q,
+							input: {}
+						}).then(({ canceled, result: a }) => {
+							ok(a);
+						});
+					});
+				},
+				out: (value) => {
+					this.logs.push({
+						id: Math.random(),
+						text: value.type === 'str' ? value.value : utils.valToString(value),
+						print: true
+					});
+				},
+				log: (type, params) => {
+					switch (type) {
+						case 'end': this.logs.push({
+							id: Math.random(),
+							text: utils.valToString(params.val, true),
+							print: false
+						}); break;
+						default: break;
+					}
+				}
+			});
+
+			let ast;
+			try {
+				ast = parse(this.props.script);
+			} catch (e) {
+				os.dialog({
+					type: 'error',
+					text: 'Syntax error :('
+				});
+				return;
+			}
+			try {
+				await aiscript.exec(ast);
+			} catch (e) {
+				os.dialog({
+					type: 'error',
+					text: e
+				});
+			}
+		},
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.uylguesu {
+	text-align: right;
+
+	> textarea {
+		display: block;
+		width: 100%;
+		max-width: 100%;
+		min-width: 100%;
+		padding: 16px;
+		color: var(--fg);
+		background: transparent;
+		border: none;
+		border-bottom: solid 1px var(--divider);
+		border-radius: 0;
+		box-sizing: border-box;
+		font: inherit;
+
+		&:focus {
+			outline: none;
+		}
+	}
+
+	> button {
+		display: inline-block;
+		margin: 8px;
+		padding: 0 10px;
+		height: 28px;
+		outline: none;
+		border-radius: 4px;
+
+		&:disabled {
+			opacity: 0.7;
+			cursor: default;
+		}
+	}
+
+	> .logs {
+		border-top: solid 1px var(--divider);
+		text-align: left;
+		padding: 16px;
+
+		&:empty {
+			display: none;
+		}
+
+		> .log {
+			&:not(.print) {
+				opacity: 0.7;
+			}
+		}
+	}
+}
+</style>
diff --git a/src/client/widgets/index.ts b/src/client/widgets/index.ts
index 0c7e82430633ab59b0e6f7bf68d32ac9fa122847..38cb85494a725cd02e00e5f409303951ccd5717c 100644
--- a/src/client/widgets/index.ts
+++ b/src/client/widgets/index.ts
@@ -18,6 +18,7 @@ export default function(app: App) {
 	app.component('MkwOnlineUsers', defineAsyncComponent(() => import('./online-users.vue')));
 	app.component('MkwJobQueue', defineAsyncComponent(() => import('./job-queue.vue')));
 	app.component('MkwButton', defineAsyncComponent(() => import('./button.vue')));
+	app.component('MkwAiscript', defineAsyncComponent(() => import('./aiscript.vue')));
 }
 
 export const widgets = [
@@ -38,4 +39,5 @@ export const widgets = [
 	'onlineUsers',
 	'jobQueue',
 	'button',
+	'aiscript',
 ];
diff --git a/src/client/widgets/memo.vue b/src/client/widgets/memo.vue
index dab19cd16e42247eaf31cbc5663ae8ee33b45a8f..3512429e0db7c8aab60a0f7d86e5bbfa153272ad 100644
--- a/src/client/widgets/memo.vue
+++ b/src/client/widgets/memo.vue
@@ -74,12 +74,18 @@ export default defineComponent({
 		max-width: 100%;
 		min-width: 100%;
 		padding: 16px;
-		color: var(--inputText);
-		background: var(--face);
+		color: var(--fg);
+		background: transparent;
 		border: none;
-		border-bottom: solid var(--lineWidth) var(--faceDivider);
+		border-bottom: solid 1px var(--divider);
 		border-radius: 0;
 		box-sizing: border-box;
+		font: inherit;
+		font-size: 0.9em;
+
+		&:focus {
+			outline: none;
+		}
 	}
 
 	> button {