@dreb/telegram 2.27.3 → 2.28.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.
- package/README.md +1 -0
- package/dist/commands/core.d.ts.map +1 -1
- package/dist/commands/core.js +34 -10
- package/dist/commands/core.js.map +1 -1
- package/dist/util/path.d.ts +15 -0
- package/dist/util/path.d.ts.map +1 -0
- package/dist/util/path.js +65 -0
- package/dist/util/path.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ systemctl --user enable --now dreb-telegram
|
|
|
78
78
|
- `/start` — Help & command list
|
|
79
79
|
- `/new` — Start fresh session (preserves current working directory)
|
|
80
80
|
- `/new <path>` — Start fresh session in the specified directory
|
|
81
|
+
- `/new <segment> ...` — Mobile shorthand for home-relative paths, e.g. `/new projects dreb` resolves to `~/projects/dreb`; quoted spans work for segments containing spaces, e.g. `/new "My Projects" dreb`
|
|
81
82
|
- `/sessions` — List recent sessions
|
|
82
83
|
- `/resume <id>` — Resume by session ID prefix
|
|
83
84
|
- `/recent [N]` — Resend last N assistant messages
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/commands/core.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/commands/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB1D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BjG;AAED,wBAAsB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9F;AAED,wBAAsB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D5F;AAED,wBAAsB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAY1F;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5E","sourcesContent":["/**\n * Core slash commands: /start, /status, /cwd, /new, /stop, /restart\n */\n\nimport { execSync } from \"node:child_process\";\nimport { type Stats, statSync } from \"node:fs\";\nimport type { Api, Context } from \"grammy\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { resolveNewPath } from \"../util/path.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdStart(ctx: Context): Promise<void> {\n\tawait ctx.reply(\n\t\t\"🤖 *dreb Telegram*\\n\\n\" +\n\t\t\t\"Send me a message and I'll forward it to dreb.\\n\\n\" +\n\t\t\t\"*Session:*\\n\" +\n\t\t\t\"/new — Start a fresh session (keeps current directory)\\n\" +\n\t\t\t\"/new <path> — Start a fresh session in a different directory\\n\" +\n\t\t\t\"/new <segment> ... — Mobile shorthand: `projects dreb` -> `~/projects/dreb`\\n\" +\n\t\t\t\"/sessions — List recent sessions\\n\" +\n\t\t\t\"/resume <id> — Resume a session\\n\" +\n\t\t\t\"/recent \\\\[N\\\\] — Resend last N messages\\n\\n\" +\n\t\t\t\"*Agent:*\\n\" +\n\t\t\t\"/status — Connection info\\n\" +\n\t\t\t\"/cwd — Working directory\\n\" +\n\t\t\t\"/stats — Token usage & cost\\n\" +\n\t\t\t\"/compact — Compact context\\n\" +\n\t\t\t\"/agents — Background subagents\\n\" +\n\t\t\t\"/model \\\\[pattern\\\\] — View/switch model\\n\" +\n\t\t\t\"/thinking \\\\[level\\\\] — View/set thinking\\n\" +\n\t\t\t\"/skills — List available skills\\n\\n\" +\n\t\t\t\"*Control:*\\n\" +\n\t\t\t\"/stop — Interrupt current task\\n\" +\n\t\t\t\"/restart — Restart the bot\",\n\t\t{ parse_mode: \"Markdown\" },\n\t);\n}\n\nexport async function cmdStatus(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tlet version = \"unknown\";\n\tlet model = \"none\";\n\ttry {\n\t\tif (bridge?.isAlive) {\n\t\t\tversion = await bridge.getVersion();\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) model = `${state.model.provider}/${state.model.id}`;\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /status error: ${e}`);\n\t}\n\n\tconst lines = [\n\t\tbridge?.isAlive ? \"✅ Connected\" : \"⚠️ Not connected (will start on next message)\",\n\t\t`📁 Working dir: \\`${config.workingDir}\\``,\n\t\t`🔧 dreb ${version}`,\n\t\t`🧠 Model: ${model}`,\n\t];\n\n\tif (bridge?.sessionId) {\n\t\tlines.push(`📂 Session: \\`${bridge.sessionId.slice(0, 8)}...\\``);\n\t}\n\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdCwd(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst cwd = userState.effectiveCwd ?? config.workingDir;\n\tawait safeSend(ctx.api, ctx.chat!.id, `📁 Working directory: \\`${cwd}\\``);\n}\n\nexport async function cmdNew(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst pathArg = args.trim();\n\n\tif (pathArg) {\n\t\t// Resolve explicit paths or shorthand tokens to an absolute candidate.\n\t\tlet resolved: string;\n\t\ttry {\n\t\t\tresolved = resolveNewPath(pathArg);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ ${message}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet stats: Stats | undefined;\n\t\ttry {\n\t\t\tstats = statSync(resolved, { throwIfNoEntry: false });\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Cannot access directory: \\`${resolved}\\` (${message})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!stats) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Directory not found: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\t\tif (!stats.isDirectory()) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Not a directory: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tconst messageId = await safeSend(\n\t\t\tctx.api,\n\t\t\tctx.chat!.id,\n\t\t\t`🆕 Next message will start a fresh session in \\`${resolved}\\``,\n\t\t);\n\t\tif (messageId === 0) {\n\t\t\tlog(`[WARN] /new confirmation failed for \\`${resolved}\\`; leaving session state unchanged`);\n\t\t\treturn;\n\t\t}\n\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = resolved;\n\t} else {\n\t\t// Eagerly resolve CWD — never store null as a sentinel.\n\t\t// After /restart, effectiveCwd is null (in-memory state is lost),\n\t\t// so this falls back to config.workingDir deterministically.\n\t\tconst cwd = userState.effectiveCwd ?? userState.config.workingDir;\n\t\tconst messageId = await safeSend(\n\t\t\tctx.api,\n\t\t\tctx.chat!.id,\n\t\t\t`🆕 Next message will start a fresh session in \\`${cwd}\\``,\n\t\t);\n\t\tif (messageId === 0) {\n\t\t\tlog(`[WARN] /new confirmation failed for \\`${cwd}\\`; leaving session state unchanged`);\n\t\t\treturn;\n\t\t}\n\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = cwd;\n\t}\n}\n\nexport async function cmdStop(ctx: Context, _api: Api, userState: UserState): Promise<void> {\n\tuserState.stopRequested = true;\n\n\t// Abort current agent activity — like pressing Esc in the TUI.\n\t// This stops the agent, not the bridge. Session stays connected.\n\tif (userState.bridge?.isAlive) {\n\t\tawait userState.bridge.abort();\n\t}\n\n\tconst parts: string[] = [];\n\tif (userState.bridge?.isStreaming || userState.promptInFlight) parts.push(\"interrupted current task\");\n\tawait ctx.reply(parts.length > 0 ? `🛑 Stopped — ${parts.join(\", \")}.` : \"🛑 Stopped.\");\n}\n\nexport async function cmdRestart(ctx: Context, config: Config): Promise<void> {\n\tawait ctx.reply(\"🔄 Restarting...\");\n\tlog(\"[CMD] /restart — triggering systemctl restart\");\n\ttry {\n\t\texecSync(`systemctl --user restart ${config.serviceName}`, { timeout: 5000 });\n\t} catch {\n\t\t// Process will be killed by systemd restart\n\t}\n}\n"]}
|
package/dist/commands/core.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Core slash commands: /start, /status, /cwd, /new, /stop, /restart
|
|
3
3
|
*/
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { resolve } from "node:path";
|
|
5
|
+
import { statSync } from "node:fs";
|
|
6
|
+
import { resolveNewPath } from "../util/path.js";
|
|
8
7
|
import { log, safeSend } from "../util/telegram.js";
|
|
9
8
|
export async function cmdStart(ctx) {
|
|
10
9
|
await ctx.reply("🤖 *dreb Telegram*\n\n" +
|
|
@@ -12,6 +11,7 @@ export async function cmdStart(ctx) {
|
|
|
12
11
|
"*Session:*\n" +
|
|
13
12
|
"/new — Start a fresh session (keeps current directory)\n" +
|
|
14
13
|
"/new <path> — Start a fresh session in a different directory\n" +
|
|
14
|
+
"/new <segment> ... — Mobile shorthand: `projects dreb` -> `~/projects/dreb`\n" +
|
|
15
15
|
"/sessions — List recent sessions\n" +
|
|
16
16
|
"/resume <id> — Resume a session\n" +
|
|
17
17
|
"/recent \\[N\\] — Resend last N messages\n\n" +
|
|
@@ -62,29 +62,53 @@ export async function cmdCwd(ctx, config, userState) {
|
|
|
62
62
|
export async function cmdNew(ctx, userState, args) {
|
|
63
63
|
const pathArg = args.trim();
|
|
64
64
|
if (pathArg) {
|
|
65
|
-
// Resolve
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
// Resolve explicit paths or shorthand tokens to an absolute candidate.
|
|
66
|
+
let resolved;
|
|
67
|
+
try {
|
|
68
|
+
resolved = resolveNewPath(pathArg);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
await safeSend(ctx.api, ctx.chat.id, `❌ ${message}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let stats;
|
|
76
|
+
try {
|
|
77
|
+
stats = statSync(resolved, { throwIfNoEntry: false });
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
await safeSend(ctx.api, ctx.chat.id, `❌ Cannot access directory: \`${resolved}\` (${message})`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (!stats) {
|
|
69
85
|
await safeSend(ctx.api, ctx.chat.id, `❌ Directory not found: \`${resolved}\``);
|
|
70
86
|
return;
|
|
71
87
|
}
|
|
72
|
-
if (!
|
|
88
|
+
if (!stats.isDirectory()) {
|
|
73
89
|
await safeSend(ctx.api, ctx.chat.id, `❌ Not a directory: \`${resolved}\``);
|
|
74
90
|
return;
|
|
75
91
|
}
|
|
92
|
+
const messageId = await safeSend(ctx.api, ctx.chat.id, `🆕 Next message will start a fresh session in \`${resolved}\``);
|
|
93
|
+
if (messageId === 0) {
|
|
94
|
+
log(`[WARN] /new confirmation failed for \`${resolved}\`; leaving session state unchanged`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
76
97
|
userState.newSessionFlag = true;
|
|
77
98
|
userState.newSessionCwd = resolved;
|
|
78
|
-
await ctx.reply(`🆕 Next message will start a fresh session in \`${resolved}\``);
|
|
79
99
|
}
|
|
80
100
|
else {
|
|
81
101
|
// Eagerly resolve CWD — never store null as a sentinel.
|
|
82
102
|
// After /restart, effectiveCwd is null (in-memory state is lost),
|
|
83
103
|
// so this falls back to config.workingDir deterministically.
|
|
84
104
|
const cwd = userState.effectiveCwd ?? userState.config.workingDir;
|
|
105
|
+
const messageId = await safeSend(ctx.api, ctx.chat.id, `🆕 Next message will start a fresh session in \`${cwd}\``);
|
|
106
|
+
if (messageId === 0) {
|
|
107
|
+
log(`[WARN] /new confirmation failed for \`${cwd}\`; leaving session state unchanged`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
85
110
|
userState.newSessionFlag = true;
|
|
86
111
|
userState.newSessionCwd = cwd;
|
|
87
|
-
await ctx.reply(`🆕 Next message will start a fresh session in \`${cwd}\``);
|
|
88
112
|
}
|
|
89
113
|
}
|
|
90
114
|
export async function cmdStop(ctx, _api, userState) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/commands/core.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core slash commands: /start, /status, /cwd, /new, /stop, /restart\n */\n\nimport { execSync } from \"node:child_process\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\nimport type { Api, Context } from \"grammy\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdStart(ctx: Context): Promise<void> {\n\tawait ctx.reply(\n\t\t\"🤖 *dreb Telegram*\\n\\n\" +\n\t\t\t\"Send me a message and I'll forward it to dreb.\\n\\n\" +\n\t\t\t\"*Session:*\\n\" +\n\t\t\t\"/new — Start a fresh session (keeps current directory)\\n\" +\n\t\t\t\"/new <path> — Start a fresh session in a different directory\\n\" +\n\t\t\t\"/sessions — List recent sessions\\n\" +\n\t\t\t\"/resume <id> — Resume a session\\n\" +\n\t\t\t\"/recent \\\\[N\\\\] — Resend last N messages\\n\\n\" +\n\t\t\t\"*Agent:*\\n\" +\n\t\t\t\"/status — Connection info\\n\" +\n\t\t\t\"/cwd — Working directory\\n\" +\n\t\t\t\"/stats — Token usage & cost\\n\" +\n\t\t\t\"/compact — Compact context\\n\" +\n\t\t\t\"/agents — Background subagents\\n\" +\n\t\t\t\"/model \\\\[pattern\\\\] — View/switch model\\n\" +\n\t\t\t\"/thinking \\\\[level\\\\] — View/set thinking\\n\" +\n\t\t\t\"/skills — List available skills\\n\\n\" +\n\t\t\t\"*Control:*\\n\" +\n\t\t\t\"/stop — Interrupt current task\\n\" +\n\t\t\t\"/restart — Restart the bot\",\n\t\t{ parse_mode: \"Markdown\" },\n\t);\n}\n\nexport async function cmdStatus(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tlet version = \"unknown\";\n\tlet model = \"none\";\n\ttry {\n\t\tif (bridge?.isAlive) {\n\t\t\tversion = await bridge.getVersion();\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) model = `${state.model.provider}/${state.model.id}`;\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /status error: ${e}`);\n\t}\n\n\tconst lines = [\n\t\tbridge?.isAlive ? \"✅ Connected\" : \"⚠️ Not connected (will start on next message)\",\n\t\t`📁 Working dir: \\`${config.workingDir}\\``,\n\t\t`🔧 dreb ${version}`,\n\t\t`🧠 Model: ${model}`,\n\t];\n\n\tif (bridge?.sessionId) {\n\t\tlines.push(`📂 Session: \\`${bridge.sessionId.slice(0, 8)}...\\``);\n\t}\n\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdCwd(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst cwd = userState.effectiveCwd ?? config.workingDir;\n\tawait safeSend(ctx.api, ctx.chat!.id, `📁 Working directory: \\`${cwd}\\``);\n}\n\nexport async function cmdNew(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst pathArg = args.trim();\n\n\tif (pathArg) {\n\t\t// Resolve path (expand ~ and make absolute)\n\t\tconst expanded = pathArg.startsWith(\"~\") ? pathArg.replace(\"~\", homedir()) : pathArg;\n\t\tconst resolved = resolve(expanded);\n\n\t\tif (!existsSync(resolved)) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Directory not found: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\t\tif (!statSync(resolved).isDirectory()) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Not a directory: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = resolved;\n\t\tawait ctx.reply(`🆕 Next message will start a fresh session in \\`${resolved}\\``);\n\t} else {\n\t\t// Eagerly resolve CWD — never store null as a sentinel.\n\t\t// After /restart, effectiveCwd is null (in-memory state is lost),\n\t\t// so this falls back to config.workingDir deterministically.\n\t\tconst cwd = userState.effectiveCwd ?? userState.config.workingDir;\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = cwd;\n\t\tawait ctx.reply(`🆕 Next message will start a fresh session in \\`${cwd}\\``);\n\t}\n}\n\nexport async function cmdStop(ctx: Context, _api: Api, userState: UserState): Promise<void> {\n\tuserState.stopRequested = true;\n\n\t// Abort current agent activity — like pressing Esc in the TUI.\n\t// This stops the agent, not the bridge. Session stays connected.\n\tif (userState.bridge?.isAlive) {\n\t\tawait userState.bridge.abort();\n\t}\n\n\tconst parts: string[] = [];\n\tif (userState.bridge?.isStreaming || userState.promptInFlight) parts.push(\"interrupted current task\");\n\tawait ctx.reply(parts.length > 0 ? `🛑 Stopped — ${parts.join(\", \")}.` : \"🛑 Stopped.\");\n}\n\nexport async function cmdRestart(ctx: Context, config: Config): Promise<void> {\n\tawait ctx.reply(\"🔄 Restarting...\");\n\tlog(\"[CMD] /restart — triggering systemctl restart\");\n\ttry {\n\t\texecSync(`systemctl --user restart ${config.serviceName}`, { timeout: 5000 });\n\t} catch {\n\t\t// Process will be killed by systemd restart\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/commands/core.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Core slash commands: /start, /status, /cwd, /new, /stop, /restart\n */\n\nimport { execSync } from \"node:child_process\";\nimport { type Stats, statSync } from \"node:fs\";\nimport type { Api, Context } from \"grammy\";\nimport type { Config } from \"../config.js\";\nimport type { UserState } from \"../types.js\";\nimport { resolveNewPath } from \"../util/path.js\";\nimport { log, safeSend } from \"../util/telegram.js\";\n\nexport async function cmdStart(ctx: Context): Promise<void> {\n\tawait ctx.reply(\n\t\t\"🤖 *dreb Telegram*\\n\\n\" +\n\t\t\t\"Send me a message and I'll forward it to dreb.\\n\\n\" +\n\t\t\t\"*Session:*\\n\" +\n\t\t\t\"/new — Start a fresh session (keeps current directory)\\n\" +\n\t\t\t\"/new <path> — Start a fresh session in a different directory\\n\" +\n\t\t\t\"/new <segment> ... — Mobile shorthand: `projects dreb` -> `~/projects/dreb`\\n\" +\n\t\t\t\"/sessions — List recent sessions\\n\" +\n\t\t\t\"/resume <id> — Resume a session\\n\" +\n\t\t\t\"/recent \\\\[N\\\\] — Resend last N messages\\n\\n\" +\n\t\t\t\"*Agent:*\\n\" +\n\t\t\t\"/status — Connection info\\n\" +\n\t\t\t\"/cwd — Working directory\\n\" +\n\t\t\t\"/stats — Token usage & cost\\n\" +\n\t\t\t\"/compact — Compact context\\n\" +\n\t\t\t\"/agents — Background subagents\\n\" +\n\t\t\t\"/model \\\\[pattern\\\\] — View/switch model\\n\" +\n\t\t\t\"/thinking \\\\[level\\\\] — View/set thinking\\n\" +\n\t\t\t\"/skills — List available skills\\n\\n\" +\n\t\t\t\"*Control:*\\n\" +\n\t\t\t\"/stop — Interrupt current task\\n\" +\n\t\t\t\"/restart — Restart the bot\",\n\t\t{ parse_mode: \"Markdown\" },\n\t);\n}\n\nexport async function cmdStatus(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst chatId = ctx.chat!.id;\n\tconst bridge = userState.bridge;\n\n\tlet version = \"unknown\";\n\tlet model = \"none\";\n\ttry {\n\t\tif (bridge?.isAlive) {\n\t\t\tversion = await bridge.getVersion();\n\t\t\tconst state = await bridge.getState();\n\t\t\tif (state?.model) model = `${state.model.provider}/${state.model.id}`;\n\t\t}\n\t} catch (e) {\n\t\tlog(`[CMD] /status error: ${e}`);\n\t}\n\n\tconst lines = [\n\t\tbridge?.isAlive ? \"✅ Connected\" : \"⚠️ Not connected (will start on next message)\",\n\t\t`📁 Working dir: \\`${config.workingDir}\\``,\n\t\t`🔧 dreb ${version}`,\n\t\t`🧠 Model: ${model}`,\n\t];\n\n\tif (bridge?.sessionId) {\n\t\tlines.push(`📂 Session: \\`${bridge.sessionId.slice(0, 8)}...\\``);\n\t}\n\n\tawait safeSend(ctx.api, chatId, lines.join(\"\\n\"));\n}\n\nexport async function cmdCwd(ctx: Context, config: Config, userState: UserState): Promise<void> {\n\tconst cwd = userState.effectiveCwd ?? config.workingDir;\n\tawait safeSend(ctx.api, ctx.chat!.id, `📁 Working directory: \\`${cwd}\\``);\n}\n\nexport async function cmdNew(ctx: Context, userState: UserState, args: string): Promise<void> {\n\tconst pathArg = args.trim();\n\n\tif (pathArg) {\n\t\t// Resolve explicit paths or shorthand tokens to an absolute candidate.\n\t\tlet resolved: string;\n\t\ttry {\n\t\t\tresolved = resolveNewPath(pathArg);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ ${message}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet stats: Stats | undefined;\n\t\ttry {\n\t\t\tstats = statSync(resolved, { throwIfNoEntry: false });\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Cannot access directory: \\`${resolved}\\` (${message})`);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!stats) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Directory not found: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\t\tif (!stats.isDirectory()) {\n\t\t\tawait safeSend(ctx.api, ctx.chat!.id, `❌ Not a directory: \\`${resolved}\\``);\n\t\t\treturn;\n\t\t}\n\n\t\tconst messageId = await safeSend(\n\t\t\tctx.api,\n\t\t\tctx.chat!.id,\n\t\t\t`🆕 Next message will start a fresh session in \\`${resolved}\\``,\n\t\t);\n\t\tif (messageId === 0) {\n\t\t\tlog(`[WARN] /new confirmation failed for \\`${resolved}\\`; leaving session state unchanged`);\n\t\t\treturn;\n\t\t}\n\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = resolved;\n\t} else {\n\t\t// Eagerly resolve CWD — never store null as a sentinel.\n\t\t// After /restart, effectiveCwd is null (in-memory state is lost),\n\t\t// so this falls back to config.workingDir deterministically.\n\t\tconst cwd = userState.effectiveCwd ?? userState.config.workingDir;\n\t\tconst messageId = await safeSend(\n\t\t\tctx.api,\n\t\t\tctx.chat!.id,\n\t\t\t`🆕 Next message will start a fresh session in \\`${cwd}\\``,\n\t\t);\n\t\tif (messageId === 0) {\n\t\t\tlog(`[WARN] /new confirmation failed for \\`${cwd}\\`; leaving session state unchanged`);\n\t\t\treturn;\n\t\t}\n\n\t\tuserState.newSessionFlag = true;\n\t\tuserState.newSessionCwd = cwd;\n\t}\n}\n\nexport async function cmdStop(ctx: Context, _api: Api, userState: UserState): Promise<void> {\n\tuserState.stopRequested = true;\n\n\t// Abort current agent activity — like pressing Esc in the TUI.\n\t// This stops the agent, not the bridge. Session stays connected.\n\tif (userState.bridge?.isAlive) {\n\t\tawait userState.bridge.abort();\n\t}\n\n\tconst parts: string[] = [];\n\tif (userState.bridge?.isStreaming || userState.promptInFlight) parts.push(\"interrupted current task\");\n\tawait ctx.reply(parts.length > 0 ? `🛑 Stopped — ${parts.join(\", \")}.` : \"🛑 Stopped.\");\n}\n\nexport async function cmdRestart(ctx: Context, config: Config): Promise<void> {\n\tawait ctx.reply(\"🔄 Restarting...\");\n\tlog(\"[CMD] /restart — triggering systemctl restart\");\n\ttry {\n\t\texecSync(`systemctl --user restart ${config.serviceName}`, { timeout: 5000 });\n\t} catch {\n\t\t// Process will be killed by systemd restart\n\t}\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a `/new` command path argument into an absolute path.
|
|
4
|
+
*
|
|
5
|
+
* Rules:
|
|
6
|
+
* - Empty input is not handled here; callers should treat it as bare `/new`.
|
|
7
|
+
* - Arguments that already look like explicit paths (start with `~` or `/`, or
|
|
8
|
+
* contain any `/`) are resolved as-is, expanding `~` to the user's home dir.
|
|
9
|
+
* - Otherwise, whitespace-separated tokens are treated as shorthand path
|
|
10
|
+
* segments under `~`. Double-quoted spans are kept as a single segment,
|
|
11
|
+
* allowing directory names that contain spaces:
|
|
12
|
+
* `"My Projects" dreb` -> `~/My Projects/dreb`
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveNewPath(pathArg: string, getHome?: typeof homedir): string;
|
|
15
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,iBAAU,GAAG,MAAM,CAwBzE","sourcesContent":["import { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\n/**\n * Parse a `/new` command path argument into an absolute path.\n *\n * Rules:\n * - Empty input is not handled here; callers should treat it as bare `/new`.\n * - Arguments that already look like explicit paths (start with `~` or `/`, or\n * contain any `/`) are resolved as-is, expanding `~` to the user's home dir.\n * - Otherwise, whitespace-separated tokens are treated as shorthand path\n * segments under `~`. Double-quoted spans are kept as a single segment,\n * allowing directory names that contain spaces:\n * `\"My Projects\" dreb` -> `~/My Projects/dreb`\n */\nexport function resolveNewPath(pathArg: string, getHome = homedir): string {\n\tconst trimmed = pathArg.trim();\n\n\tif (!trimmed) {\n\t\tthrow new Error(\"resolveNewPath does not handle bare /new; pass a non-empty path argument\");\n\t}\n\n\t// Explicit path forms: keep existing behavior.\n\tif (trimmed.startsWith(\"~\") || trimmed.startsWith(\"/\") || trimmed.includes(\"/\")) {\n\t\tconst expanded = trimmed.startsWith(\"~\") ? trimmed.replace(\"~\", getHome()) : trimmed;\n\t\treturn resolve(expanded);\n\t}\n\n\t// Shorthand form: tokens under the home directory.\n\tconst segments = parseShorthandTokens(trimmed);\n\tif (segments.length === 0) {\n\t\tthrow new Error(\"Invalid /new shorthand: no path segments were provided\");\n\t}\n\tif (segments.some((segment) => segment === \".\" || segment === \"..\")) {\n\t\tthrow new Error(\"Invalid /new shorthand: path segments cannot be '.' or '..'\");\n\t}\n\n\tconst homeRelative = [\"~\", ...segments].join(\"/\");\n\treturn resolve(homeRelative.replace(\"~\", getHome()));\n}\n\n/**\n * Split shorthand input into path segments, respecting double-quoted spans.\n */\nfunction parseShorthandTokens(input: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet inQuote = false;\n\n\tfor (const char of input) {\n\t\tif (char === '\"') {\n\t\t\tinQuote = !inQuote;\n\t\t} else if (/\\s/.test(char) && !inQuote) {\n\t\t\tif (current.trim().length > 0) {\n\t\t\t\ttokens.push(current);\n\t\t\t}\n\t\t\tcurrent = \"\";\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (inQuote) {\n\t\tthrow new Error(\"Invalid /new shorthand: missing closing quote\");\n\t}\n\n\tif (current.trim().length > 0) {\n\t\ttokens.push(current);\n\t}\n\n\treturn tokens;\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Parse a `/new` command path argument into an absolute path.
|
|
5
|
+
*
|
|
6
|
+
* Rules:
|
|
7
|
+
* - Empty input is not handled here; callers should treat it as bare `/new`.
|
|
8
|
+
* - Arguments that already look like explicit paths (start with `~` or `/`, or
|
|
9
|
+
* contain any `/`) are resolved as-is, expanding `~` to the user's home dir.
|
|
10
|
+
* - Otherwise, whitespace-separated tokens are treated as shorthand path
|
|
11
|
+
* segments under `~`. Double-quoted spans are kept as a single segment,
|
|
12
|
+
* allowing directory names that contain spaces:
|
|
13
|
+
* `"My Projects" dreb` -> `~/My Projects/dreb`
|
|
14
|
+
*/
|
|
15
|
+
export function resolveNewPath(pathArg, getHome = homedir) {
|
|
16
|
+
const trimmed = pathArg.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
throw new Error("resolveNewPath does not handle bare /new; pass a non-empty path argument");
|
|
19
|
+
}
|
|
20
|
+
// Explicit path forms: keep existing behavior.
|
|
21
|
+
if (trimmed.startsWith("~") || trimmed.startsWith("/") || trimmed.includes("/")) {
|
|
22
|
+
const expanded = trimmed.startsWith("~") ? trimmed.replace("~", getHome()) : trimmed;
|
|
23
|
+
return resolve(expanded);
|
|
24
|
+
}
|
|
25
|
+
// Shorthand form: tokens under the home directory.
|
|
26
|
+
const segments = parseShorthandTokens(trimmed);
|
|
27
|
+
if (segments.length === 0) {
|
|
28
|
+
throw new Error("Invalid /new shorthand: no path segments were provided");
|
|
29
|
+
}
|
|
30
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
31
|
+
throw new Error("Invalid /new shorthand: path segments cannot be '.' or '..'");
|
|
32
|
+
}
|
|
33
|
+
const homeRelative = ["~", ...segments].join("/");
|
|
34
|
+
return resolve(homeRelative.replace("~", getHome()));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Split shorthand input into path segments, respecting double-quoted spans.
|
|
38
|
+
*/
|
|
39
|
+
function parseShorthandTokens(input) {
|
|
40
|
+
const tokens = [];
|
|
41
|
+
let current = "";
|
|
42
|
+
let inQuote = false;
|
|
43
|
+
for (const char of input) {
|
|
44
|
+
if (char === '"') {
|
|
45
|
+
inQuote = !inQuote;
|
|
46
|
+
}
|
|
47
|
+
else if (/\s/.test(char) && !inQuote) {
|
|
48
|
+
if (current.trim().length > 0) {
|
|
49
|
+
tokens.push(current);
|
|
50
|
+
}
|
|
51
|
+
current = "";
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
current += char;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (inQuote) {
|
|
58
|
+
throw new Error("Invalid /new shorthand: missing closing quote");
|
|
59
|
+
}
|
|
60
|
+
if (current.trim().length > 0) {
|
|
61
|
+
tokens.push(current);
|
|
62
|
+
}
|
|
63
|
+
return tokens;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/util/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,OAAO,GAAG,OAAO,EAAU;IAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAE/B,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC7F,CAAC;IAED,+CAA+C;IAC/C,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjF,MAAM,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;QACrF,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAED,mDAAmD;IACnD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClD,OAAO,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAAA,CACrD;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,KAAa,EAAY;IACtD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YAClB,OAAO,GAAG,CAAC,OAAO,CAAC;QACpB,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;YACD,OAAO,GAAG,EAAE,CAAC;QACd,CAAC;aAAM,CAAC;YACP,OAAO,IAAI,IAAI,CAAC;QACjB,CAAC;IACF,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAClE,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,MAAM,CAAC;AAAA,CACd","sourcesContent":["import { homedir } from \"node:os\";\nimport { resolve } from \"node:path\";\n\n/**\n * Parse a `/new` command path argument into an absolute path.\n *\n * Rules:\n * - Empty input is not handled here; callers should treat it as bare `/new`.\n * - Arguments that already look like explicit paths (start with `~` or `/`, or\n * contain any `/`) are resolved as-is, expanding `~` to the user's home dir.\n * - Otherwise, whitespace-separated tokens are treated as shorthand path\n * segments under `~`. Double-quoted spans are kept as a single segment,\n * allowing directory names that contain spaces:\n * `\"My Projects\" dreb` -> `~/My Projects/dreb`\n */\nexport function resolveNewPath(pathArg: string, getHome = homedir): string {\n\tconst trimmed = pathArg.trim();\n\n\tif (!trimmed) {\n\t\tthrow new Error(\"resolveNewPath does not handle bare /new; pass a non-empty path argument\");\n\t}\n\n\t// Explicit path forms: keep existing behavior.\n\tif (trimmed.startsWith(\"~\") || trimmed.startsWith(\"/\") || trimmed.includes(\"/\")) {\n\t\tconst expanded = trimmed.startsWith(\"~\") ? trimmed.replace(\"~\", getHome()) : trimmed;\n\t\treturn resolve(expanded);\n\t}\n\n\t// Shorthand form: tokens under the home directory.\n\tconst segments = parseShorthandTokens(trimmed);\n\tif (segments.length === 0) {\n\t\tthrow new Error(\"Invalid /new shorthand: no path segments were provided\");\n\t}\n\tif (segments.some((segment) => segment === \".\" || segment === \"..\")) {\n\t\tthrow new Error(\"Invalid /new shorthand: path segments cannot be '.' or '..'\");\n\t}\n\n\tconst homeRelative = [\"~\", ...segments].join(\"/\");\n\treturn resolve(homeRelative.replace(\"~\", getHome()));\n}\n\n/**\n * Split shorthand input into path segments, respecting double-quoted spans.\n */\nfunction parseShorthandTokens(input: string): string[] {\n\tconst tokens: string[] = [];\n\tlet current = \"\";\n\tlet inQuote = false;\n\n\tfor (const char of input) {\n\t\tif (char === '\"') {\n\t\t\tinQuote = !inQuote;\n\t\t} else if (/\\s/.test(char) && !inQuote) {\n\t\t\tif (current.trim().length > 0) {\n\t\t\t\ttokens.push(current);\n\t\t\t}\n\t\t\tcurrent = \"\";\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (inQuote) {\n\t\tthrow new Error(\"Invalid /new shorthand: missing closing quote\");\n\t}\n\n\tif (current.trim().length > 0) {\n\t\ttokens.push(current);\n\t}\n\n\treturn tokens;\n}\n"]}
|