arisa 2.0.1 → 2.0.3
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.
- package/README.md +5 -18
- package/bin/arisa.js +13 -3
- package/package.json +1 -1
- package/src/core/index.ts +4 -4
- package/src/core/onboarding.ts +4 -4
- package/src/core/processor.ts +4 -3
- package/src/daemon/codex-login.ts +37 -3
- package/src/daemon/index.ts +5 -2
- package/src/daemon/setup.ts +12 -2
package/README.md
CHANGED
|
@@ -13,20 +13,14 @@ Arisa can execute actions with operational control over the system where it runs
|
|
|
13
13
|
## Commands
|
|
14
14
|
|
|
15
15
|
Requires [Bun](https://bun.sh).
|
|
16
|
-
For Bun global installs, use your user environment
|
|
17
|
-
If needed, configure Bun's user-local install directory:
|
|
16
|
+
For Bun global installs, use your user environment.
|
|
18
17
|
|
|
19
18
|
```bash
|
|
20
|
-
|
|
21
|
-
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
bun install -g arisa # Global install from package registry (recommended)
|
|
26
|
-
npm install -g arisa # Alternative global install via npm (may require sudo)
|
|
19
|
+
bun add -g arisa # Global install from package registry (recommended)
|
|
27
20
|
```
|
|
28
21
|
|
|
29
22
|
```bash
|
|
23
|
+
arisa # Foreground daemon mode (Ctrl+C to stop)
|
|
30
24
|
arisa start # Start as service (enables autostart with systemd --user)
|
|
31
25
|
arisa stop # Stop service
|
|
32
26
|
arisa status # Service status
|
|
@@ -36,13 +30,6 @@ arisa core # Foreground core-only mode
|
|
|
36
30
|
arisa dev # Foreground core watch mode
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
```bash
|
|
40
|
-
bun install # Install dependencies
|
|
41
|
-
bun run daemon # Start everything (Daemon spawns Core with --watch)
|
|
42
|
-
bun run dev # Start Core only with hot-reload (for development)
|
|
43
|
-
npm install -g . # Global install via Node/npm
|
|
44
|
-
bun add -g . # Global install via Bun
|
|
45
|
-
```
|
|
46
33
|
|
|
47
34
|
On Linux with `systemd --user`, `arisa start` enables auto-start on reboot. To keep it running even without an active login session:
|
|
48
35
|
|
|
@@ -95,12 +82,12 @@ src/
|
|
|
95
82
|
│ ├── media.ts # Voice transcription (Whisper), image analysis (Vision), speech synthesis (ElevenLabs)
|
|
96
83
|
│ ├── scheduler.ts # Cron + one-time tasks with croner, persists via deepbase
|
|
97
84
|
│ ├── format.ts # Telegram chunking (4096 char limit)
|
|
98
|
-
│ ├── file-detector.ts
|
|
85
|
+
│ ├── file-detector.ts # Detect file paths in responses for auto-sending
|
|
99
86
|
│ └── context.ts # Manage -c flag and reset_flag
|
|
100
87
|
│
|
|
101
88
|
└── shared/
|
|
102
89
|
├── types.ts # All shared interfaces
|
|
103
|
-
├── config.ts
|
|
90
|
+
├── config.ts # Env vars, ports, paths
|
|
104
91
|
├── logger.ts # Logger → .arisa/logs/
|
|
105
92
|
└── db.ts # Unified persistence layer (deepbase)
|
|
106
93
|
```
|
package/bin/arisa.js
CHANGED
|
@@ -27,15 +27,17 @@ const systemdUserDir = join(homeDir, ".config", "systemd", "user");
|
|
|
27
27
|
const systemdUserUnitPath = join(systemdUserDir, systemdServiceName);
|
|
28
28
|
|
|
29
29
|
const args = process.argv.slice(2);
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const inputCommand = (args[0] || "").toLowerCase();
|
|
31
|
+
const command = inputCommand || "daemon";
|
|
32
|
+
const rest = inputCommand ? args.slice(1) : args;
|
|
33
|
+
const isDefaultInvocation = inputCommand === "";
|
|
32
34
|
|
|
33
35
|
function printHelp() {
|
|
34
36
|
process.stdout.write(
|
|
35
37
|
`Arisa CLI
|
|
36
38
|
|
|
37
39
|
Usage:
|
|
38
|
-
arisa Start
|
|
40
|
+
arisa Start daemon in foreground (default)
|
|
39
41
|
arisa start Start service and enable restart-on-boot
|
|
40
42
|
arisa stop Stop service
|
|
41
43
|
arisa status Show service status
|
|
@@ -396,6 +398,11 @@ function restartService() {
|
|
|
396
398
|
return restartDetachedFallback();
|
|
397
399
|
}
|
|
398
400
|
|
|
401
|
+
function printForegroundNotice() {
|
|
402
|
+
process.stdout.write("Starting Arisa in foreground. Press Ctrl+C to stop.\n");
|
|
403
|
+
process.stdout.write("Use `arisa start` to run it as a background service.\n");
|
|
404
|
+
}
|
|
405
|
+
|
|
399
406
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
400
407
|
printHelp();
|
|
401
408
|
process.exit(0);
|
|
@@ -421,6 +428,9 @@ switch (command) {
|
|
|
421
428
|
break;
|
|
422
429
|
case "daemon":
|
|
423
430
|
case "run": {
|
|
431
|
+
if (isDefaultInvocation) {
|
|
432
|
+
printForegroundNotice();
|
|
433
|
+
}
|
|
424
434
|
const child = runWithBun([daemonEntry, ...rest]);
|
|
425
435
|
process.exit(child.status === null ? 1 : child.status);
|
|
426
436
|
}
|
package/package.json
CHANGED
package/src/core/index.ts
CHANGED
|
@@ -164,8 +164,8 @@ ${messageText}`;
|
|
|
164
164
|
const deps = checkDeps();
|
|
165
165
|
if (!deps.codex) {
|
|
166
166
|
const hint = deps.os === "macOS"
|
|
167
|
-
? "<code>
|
|
168
|
-
: "<code>
|
|
167
|
+
? "<code>bun add -g @openai/codex</code>"
|
|
168
|
+
: "<code>bun add -g @openai/codex</code>";
|
|
169
169
|
return Response.json({ text: `Codex CLI is not installed.\n${hint}` } as CoreResponse);
|
|
170
170
|
}
|
|
171
171
|
backendState.set(msg.chatId, "codex");
|
|
@@ -179,8 +179,8 @@ ${messageText}`;
|
|
|
179
179
|
const deps = checkDeps();
|
|
180
180
|
if (!deps.claude) {
|
|
181
181
|
const hint = deps.os === "macOS"
|
|
182
|
-
? "<code>brew install claude-code</code>
|
|
183
|
-
: "<code>
|
|
182
|
+
? "<code>brew install claude-code</code> or <code>bun add -g @anthropic-ai/claude-code</code>"
|
|
183
|
+
: "<code>bun add -g @anthropic-ai/claude-code</code>";
|
|
184
184
|
return Response.json({ text: `Claude CLI is not installed.\n${hint}` } as CoreResponse);
|
|
185
185
|
}
|
|
186
186
|
backendState.set(msg.chatId, "claude");
|
package/src/core/onboarding.ts
CHANGED
|
@@ -86,9 +86,9 @@ export async function getOnboarding(chatId: string): Promise<{ message: string;
|
|
|
86
86
|
if (deps.os === "macOS") {
|
|
87
87
|
lines.push("Claude: <code>brew install claude-code</code>");
|
|
88
88
|
} else {
|
|
89
|
-
lines.push("Claude: <code>
|
|
89
|
+
lines.push("Claude: <code>bun add -g @anthropic-ai/claude-code</code>");
|
|
90
90
|
}
|
|
91
|
-
lines.push("Codex: <code>
|
|
91
|
+
lines.push("Codex: <code>bun add -g @openai/codex</code>\n");
|
|
92
92
|
lines.push("Install one and message me again.");
|
|
93
93
|
return { message: lines.join("\n"), blocking: true };
|
|
94
94
|
}
|
|
@@ -100,9 +100,9 @@ export async function getOnboarding(chatId: string): Promise<{ message: string;
|
|
|
100
100
|
const lines = [`<b>Arisa</b> — using <b>${using}</b>`];
|
|
101
101
|
|
|
102
102
|
if (!deps.claude) {
|
|
103
|
-
lines.push("Claude CLI not installed. Add it with <code>
|
|
103
|
+
lines.push("Claude CLI not installed. Add it with <code>bun add -g @anthropic-ai/claude-code</code>");
|
|
104
104
|
} else if (!deps.codex) {
|
|
105
|
-
lines.push("Codex CLI not installed. Add it with <code>
|
|
105
|
+
lines.push("Codex CLI not installed. Add it with <code>bun add -g @openai/codex</code>");
|
|
106
106
|
} else {
|
|
107
107
|
lines.push("Use /codex or /claude to switch backend.");
|
|
108
108
|
}
|
package/src/core/processor.ts
CHANGED
|
@@ -24,10 +24,11 @@ const ACTIVITY_LOG = join(config.logsDir, "activity.log");
|
|
|
24
24
|
const PROMPT_PREVIEW_MAX = 220;
|
|
25
25
|
export const CLAUDE_RATE_LIMIT_MESSAGE = "Claude is out of credits right now. Please try again in a few minutes.";
|
|
26
26
|
export const CODEX_AUTH_REQUIRED_MESSAGE = [
|
|
27
|
-
"Codex is
|
|
28
|
-
"Check the
|
|
27
|
+
"Codex login is required.",
|
|
28
|
+
"Check the Arisa daemon logs now and complete the device-auth steps shown there.",
|
|
29
|
+
"If the login flow is not running, execute:",
|
|
29
30
|
"<code>codex login --device-auth</code>",
|
|
30
|
-
"Then
|
|
31
|
+
"Then send your message again.",
|
|
31
32
|
].join("\n");
|
|
32
33
|
|
|
33
34
|
function logActivity(backend: string, model: string | null, durationMs: number, status: string) {
|
|
@@ -23,13 +23,22 @@ const RETRY_COOLDOWN_MS = 30_000;
|
|
|
23
23
|
|
|
24
24
|
let loginInProgress = false;
|
|
25
25
|
let lastLoginAttemptAt = 0;
|
|
26
|
+
const pendingChatIds = new Set<string>();
|
|
27
|
+
|
|
28
|
+
type NotifyFn = (chatId: string, text: string) => Promise<void>;
|
|
29
|
+
let notifyFn: NotifyFn | null = null;
|
|
30
|
+
|
|
31
|
+
export function setCodexLoginNotify(fn: NotifyFn) {
|
|
32
|
+
notifyFn = fn;
|
|
33
|
+
}
|
|
26
34
|
|
|
27
35
|
function needsCodexLogin(text: string): boolean {
|
|
28
36
|
return AUTH_HINT_PATTERNS.some((pattern) => pattern.test(text));
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
export function maybeStartCodexDeviceAuth(rawCoreText: string): void {
|
|
39
|
+
export function maybeStartCodexDeviceAuth(rawCoreText: string, chatId?: string): void {
|
|
32
40
|
if (!rawCoreText || !needsCodexLogin(rawCoreText)) return;
|
|
41
|
+
if (chatId) pendingChatIds.add(chatId);
|
|
33
42
|
|
|
34
43
|
if (loginInProgress) {
|
|
35
44
|
log.info("Codex device auth already in progress; skipping duplicate trigger");
|
|
@@ -50,7 +59,8 @@ export function maybeStartCodexDeviceAuth(rawCoreText: string): void {
|
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
async function runCodexDeviceAuth(): Promise<void> {
|
|
53
|
-
log.warn("Codex auth
|
|
62
|
+
log.warn("Codex auth required. Starting `codex login --device-auth` now.");
|
|
63
|
+
log.warn("Complete device auth using the URL/code printed below in this Arisa terminal.");
|
|
54
64
|
|
|
55
65
|
let proc: ReturnType<typeof Bun.spawn>;
|
|
56
66
|
try {
|
|
@@ -68,8 +78,32 @@ async function runCodexDeviceAuth(): Promise<void> {
|
|
|
68
78
|
|
|
69
79
|
const exitCode = await proc.exited;
|
|
70
80
|
if (exitCode === 0) {
|
|
71
|
-
log.info("Codex device auth finished successfully");
|
|
81
|
+
log.info("Codex device auth finished successfully. You can retry your message.");
|
|
82
|
+
await notifySuccess();
|
|
72
83
|
} else {
|
|
73
84
|
log.error(`Codex device auth finished with exit code ${exitCode}`);
|
|
85
|
+
pendingChatIds.clear();
|
|
74
86
|
}
|
|
75
87
|
}
|
|
88
|
+
|
|
89
|
+
async function notifySuccess(): Promise<void> {
|
|
90
|
+
if (!notifyFn || pendingChatIds.size === 0) return;
|
|
91
|
+
|
|
92
|
+
const text = [
|
|
93
|
+
"<b>Codex login completed successfully.</b>",
|
|
94
|
+
"Then try again.",
|
|
95
|
+
].join("\n");
|
|
96
|
+
|
|
97
|
+
const chats = Array.from(pendingChatIds);
|
|
98
|
+
pendingChatIds.clear();
|
|
99
|
+
|
|
100
|
+
await Promise.all(
|
|
101
|
+
chats.map(async (chatId) => {
|
|
102
|
+
try {
|
|
103
|
+
await notifyFn?.(chatId, text);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
log.error(`Failed to send Codex login success notice to ${chatId}: ${error}`);
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
package/src/daemon/index.ts
CHANGED
|
@@ -28,7 +28,7 @@ const { TelegramChannel } = await import("./channels/telegram");
|
|
|
28
28
|
const { sendToCore } = await import("./bridge");
|
|
29
29
|
const { startCore, stopCore, setLifecycleNotify } = await import("./lifecycle");
|
|
30
30
|
const { setAutoFixNotify } = await import("./autofix");
|
|
31
|
-
const { maybeStartCodexDeviceAuth } = await import("./codex-login");
|
|
31
|
+
const { maybeStartCodexDeviceAuth, setCodexLoginNotify } = await import("./codex-login");
|
|
32
32
|
const { chunkMessage, markdownToTelegramHtml } = await import("../core/format");
|
|
33
33
|
const { saveMessageRecord } = await import("../shared/db");
|
|
34
34
|
|
|
@@ -61,6 +61,9 @@ const sendToAllChats = async (text: string) => {
|
|
|
61
61
|
|
|
62
62
|
setLifecycleNotify(sendToAllChats);
|
|
63
63
|
setAutoFixNotify(sendToAllChats);
|
|
64
|
+
setCodexLoginNotify(async (chatId, text) => {
|
|
65
|
+
await telegram.send(chatId, text);
|
|
66
|
+
});
|
|
64
67
|
|
|
65
68
|
telegram.onMessage(async (msg) => {
|
|
66
69
|
knownChatIds.add(msg.chatId);
|
|
@@ -78,7 +81,7 @@ telegram.onMessage(async (msg) => {
|
|
|
78
81
|
clearInterval(typingInterval);
|
|
79
82
|
|
|
80
83
|
const raw = response.text || "";
|
|
81
|
-
maybeStartCodexDeviceAuth(raw);
|
|
84
|
+
maybeStartCodexDeviceAuth(raw, msg.chatId);
|
|
82
85
|
const messageParts = raw.split(/\n---CHUNK---\n/g);
|
|
83
86
|
let sentText = false;
|
|
84
87
|
|
package/src/daemon/setup.ts
CHANGED
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
13
13
|
import { dirname, join } from "path";
|
|
14
14
|
import { dataDir } from "../shared/paths";
|
|
15
|
+
import { secrets } from "../shared/secrets";
|
|
15
16
|
|
|
16
17
|
const ENV_PATH = join(dataDir, ".env");
|
|
18
|
+
const SETUP_DONE_KEY = "ARISA_SETUP_COMPLETE";
|
|
17
19
|
|
|
18
20
|
function loadExistingEnv(): Record<string, string> {
|
|
19
21
|
if (!existsSync(ENV_PATH)) return {};
|
|
@@ -44,10 +46,13 @@ async function prompt(question: string): Promise<string> {
|
|
|
44
46
|
|
|
45
47
|
export async function runSetup(): Promise<boolean> {
|
|
46
48
|
const vars = loadExistingEnv();
|
|
49
|
+
const telegramSecret = await secrets.telegram();
|
|
50
|
+
const openaiSecret = await secrets.openai();
|
|
47
51
|
let changed = false;
|
|
52
|
+
const setupDone = vars[SETUP_DONE_KEY] === "1" || process.env[SETUP_DONE_KEY] === "1";
|
|
48
53
|
|
|
49
54
|
// Required: TELEGRAM_BOT_TOKEN
|
|
50
|
-
if (!vars.TELEGRAM_BOT_TOKEN && !process.env.TELEGRAM_BOT_TOKEN) {
|
|
55
|
+
if (!vars.TELEGRAM_BOT_TOKEN && !process.env.TELEGRAM_BOT_TOKEN && !telegramSecret) {
|
|
51
56
|
console.log("\n🔧 Arisa Setup\n");
|
|
52
57
|
console.log("Telegram Bot Token required. Get one from @BotFather on Telegram.");
|
|
53
58
|
const token = await prompt("TELEGRAM_BOT_TOKEN: ");
|
|
@@ -60,7 +65,7 @@ export async function runSetup(): Promise<boolean> {
|
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
// Optional: OPENAI_API_KEY
|
|
63
|
-
if (!vars.OPENAI_API_KEY && !process.env.OPENAI_API_KEY) {
|
|
68
|
+
if (!vars.OPENAI_API_KEY && !process.env.OPENAI_API_KEY && !openaiSecret && !setupDone) {
|
|
64
69
|
if (!changed) console.log("\n🔧 Arisa Setup\n");
|
|
65
70
|
console.log("\nOpenAI API Key (optional — enables voice transcription + image analysis).");
|
|
66
71
|
const key = await prompt("OPENAI_API_KEY (enter to skip): ");
|
|
@@ -70,6 +75,11 @@ export async function runSetup(): Promise<boolean> {
|
|
|
70
75
|
}
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
if (!setupDone) {
|
|
79
|
+
vars[SETUP_DONE_KEY] = "1";
|
|
80
|
+
changed = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
if (changed) {
|
|
74
84
|
saveEnv(vars);
|
|
75
85
|
console.log(`\nConfig saved to ${ENV_PATH}\n`);
|