dsclaw 0.1.0 → 0.1.1
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/dist/cli/index.js +54 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +21 -17
- package/dist/index.js +55 -34
- package/dist/index.js.map +1 -1
- package/dist/web/chat.html +18 -5
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -50,7 +50,7 @@ function createLogger(module) {
|
|
|
50
50
|
|
|
51
51
|
// src/gateway/config.ts
|
|
52
52
|
var log = createLogger("config");
|
|
53
|
-
var CONFIG_DIR = join(homedir(), ".
|
|
53
|
+
var CONFIG_DIR = join(homedir(), ".dsclaw");
|
|
54
54
|
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
55
55
|
var ConfigSchema = z.object({
|
|
56
56
|
port: z.number().default(3e3),
|
|
@@ -89,7 +89,7 @@ function configExists() {
|
|
|
89
89
|
|
|
90
90
|
// src/cli/init.ts
|
|
91
91
|
async function initCommand() {
|
|
92
|
-
console.log("\n
|
|
92
|
+
console.log("\n DSClaw Setup (optional)\n");
|
|
93
93
|
console.log(" Web chat works out of the box \u2014 this is for extra settings.\n");
|
|
94
94
|
if (configExists()) {
|
|
95
95
|
const { overwrite } = await inquirer.prompt([
|
|
@@ -131,7 +131,7 @@ async function initCommand() {
|
|
|
131
131
|
saveConfig(config);
|
|
132
132
|
console.log(`
|
|
133
133
|
Config saved to ${CONFIG_PATH}`);
|
|
134
|
-
console.log(" Run '
|
|
134
|
+
console.log(" Run 'dsclaw start' to launch.\n");
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// src/gateway/user-session.ts
|
|
@@ -154,7 +154,7 @@ var log2 = createLogger("user-session");
|
|
|
154
154
|
var ALG = "aes-256-gcm";
|
|
155
155
|
var IV_LEN = 12;
|
|
156
156
|
var TAG_LEN = 16;
|
|
157
|
-
var KEY_SEED = "
|
|
157
|
+
var KEY_SEED = "dsclaw-v1";
|
|
158
158
|
var SESSIONS_DIR = join2(CONFIG_DIR, "sessions");
|
|
159
159
|
function deriveKey() {
|
|
160
160
|
let user = "";
|
|
@@ -239,14 +239,14 @@ var ErrorCategory = {
|
|
|
239
239
|
FATAL: "FATAL",
|
|
240
240
|
DEGRADED: "DEGRADED"
|
|
241
241
|
};
|
|
242
|
-
var
|
|
242
|
+
var DSClawError = class extends Error {
|
|
243
243
|
category;
|
|
244
244
|
code;
|
|
245
245
|
retryable;
|
|
246
246
|
details;
|
|
247
247
|
constructor(opts) {
|
|
248
248
|
super(opts.message);
|
|
249
|
-
this.name = "
|
|
249
|
+
this.name = "DSClawError";
|
|
250
250
|
this.category = opts.category;
|
|
251
251
|
this.code = opts.code;
|
|
252
252
|
this.retryable = opts.retryable;
|
|
@@ -301,7 +301,7 @@ var DSersAuth = class {
|
|
|
301
301
|
}
|
|
302
302
|
async login() {
|
|
303
303
|
if (!this.config.email || !this.config.password) {
|
|
304
|
-
throw new
|
|
304
|
+
throw new DSClawError({
|
|
305
305
|
category: ErrorCategory.NEEDS_HUMAN,
|
|
306
306
|
code: "DSERS_NO_CREDENTIALS",
|
|
307
307
|
message: "DSers email and password not configured.",
|
|
@@ -322,7 +322,7 @@ var DSersAuth = class {
|
|
|
322
322
|
);
|
|
323
323
|
if (!resp.ok) {
|
|
324
324
|
const body = await resp.text();
|
|
325
|
-
throw new
|
|
325
|
+
throw new DSClawError({
|
|
326
326
|
category: resp.status === 401 || resp.status === 403 ? ErrorCategory.NEEDS_HUMAN : ErrorCategory.RETRYABLE,
|
|
327
327
|
code: `DSERS_LOGIN_${resp.status}`,
|
|
328
328
|
message: `DSers login failed (HTTP ${resp.status}): ${body.slice(0, 200)}`,
|
|
@@ -332,7 +332,7 @@ var DSersAuth = class {
|
|
|
332
332
|
const data = await resp.json();
|
|
333
333
|
const inner = data["data"];
|
|
334
334
|
if (!inner?.["sessionId"]) {
|
|
335
|
-
throw new
|
|
335
|
+
throw new DSClawError({
|
|
336
336
|
category: ErrorCategory.NEEDS_HUMAN,
|
|
337
337
|
code: "DSERS_LOGIN_NO_SESSION",
|
|
338
338
|
message: "DSers login succeeded but no session returned.",
|
|
@@ -528,7 +528,7 @@ var DSersClient = class {
|
|
|
528
528
|
}
|
|
529
529
|
if (resp.status >= 400) {
|
|
530
530
|
const category = classifyHttpError(resp.status, bodyText);
|
|
531
|
-
throw new
|
|
531
|
+
throw new DSClawError({
|
|
532
532
|
category,
|
|
533
533
|
code: `DSERS_API_${resp.status}`,
|
|
534
534
|
message: `DSers API ${method} ${path} returned ${resp.status}: ${bodyText.slice(0, 300)}`,
|
|
@@ -659,7 +659,7 @@ function getDefaultModel(provider) {
|
|
|
659
659
|
return "gpt-4o";
|
|
660
660
|
}
|
|
661
661
|
}
|
|
662
|
-
var SYSTEM_PROMPT = `You are
|
|
662
|
+
var SYSTEM_PROMPT = `You are DSClaw, a friendly AI assistant that manages dropshipping stores through DSers.
|
|
663
663
|
Your user has ZERO technical background \u2014 they are a small business owner or solo entrepreneur.
|
|
664
664
|
|
|
665
665
|
COMMUNICATION RULES:
|
|
@@ -685,9 +685,9 @@ GUIDELINES:
|
|
|
685
685
|
- For bulk operations, show a preview and ask for confirmation
|
|
686
686
|
- Use simple summaries: "You have 15 products waiting to be pushed" instead of raw JSON
|
|
687
687
|
- If the user seems confused, offer guided suggestions like "Would you like me to show your stores first?"`;
|
|
688
|
-
var
|
|
689
|
-
id = "
|
|
690
|
-
name = "
|
|
688
|
+
var DSClawCoreAgent = class {
|
|
689
|
+
id = "dsclaw-core";
|
|
690
|
+
name = "DSClaw Core Agent";
|
|
691
691
|
dsers;
|
|
692
692
|
memory;
|
|
693
693
|
llm;
|
|
@@ -999,6 +999,7 @@ var WebChatServer = class {
|
|
|
999
999
|
server = null;
|
|
1000
1000
|
wss = null;
|
|
1001
1001
|
handler = null;
|
|
1002
|
+
connectHandler = null;
|
|
1002
1003
|
connections = /* @__PURE__ */ new Map();
|
|
1003
1004
|
_connected = false;
|
|
1004
1005
|
port = 3e3;
|
|
@@ -1032,6 +1033,13 @@ var WebChatServer = class {
|
|
|
1032
1033
|
this.wsSend(ws, { type: "session", userId: clientId });
|
|
1033
1034
|
this.connections.set(clientId, ws);
|
|
1034
1035
|
log7.info({ userId: clientId }, "Web client connected");
|
|
1036
|
+
if (this.connectHandler) {
|
|
1037
|
+
try {
|
|
1038
|
+
this.connectHandler(clientId);
|
|
1039
|
+
} catch (err) {
|
|
1040
|
+
log7.error({ error: err, userId: clientId }, "Connect handler failed");
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1035
1043
|
ws.on("message", async (raw) => {
|
|
1036
1044
|
try {
|
|
1037
1045
|
const msg = JSON.parse(raw.toString());
|
|
@@ -1094,6 +1102,9 @@ var WebChatServer = class {
|
|
|
1094
1102
|
onMessage(handler) {
|
|
1095
1103
|
this.handler = handler;
|
|
1096
1104
|
}
|
|
1105
|
+
onConnect(handler) {
|
|
1106
|
+
this.connectHandler = handler;
|
|
1107
|
+
}
|
|
1097
1108
|
async send(targetUserId, message) {
|
|
1098
1109
|
if (message.card) {
|
|
1099
1110
|
await this.sendCard(targetUserId, message.card);
|
|
@@ -1406,7 +1417,7 @@ function degradationMessage(service) {
|
|
|
1406
1417
|
var log11 = createLogger("gateway");
|
|
1407
1418
|
var sessionHistories = /* @__PURE__ */ new Map();
|
|
1408
1419
|
var MAX_HISTORY = 20;
|
|
1409
|
-
var
|
|
1420
|
+
var DSClawGateway = class {
|
|
1410
1421
|
config;
|
|
1411
1422
|
channels = [];
|
|
1412
1423
|
webChannel = null;
|
|
@@ -1419,9 +1430,10 @@ var DropClawGateway = class {
|
|
|
1419
1430
|
this.memory = new FileMemoryProvider();
|
|
1420
1431
|
}
|
|
1421
1432
|
async start() {
|
|
1422
|
-
log11.info("Starting
|
|
1433
|
+
log11.info("Starting DSClaw Gateway...");
|
|
1423
1434
|
const web = new WebChatServer();
|
|
1424
1435
|
web.onMessage((msg) => this.handleMessage(web, msg));
|
|
1436
|
+
web.onConnect((userId) => this.handleWebConnect(web, userId));
|
|
1425
1437
|
await web.connect({ port: this.config.port });
|
|
1426
1438
|
this.webChannel = web;
|
|
1427
1439
|
this.channels.push(web);
|
|
@@ -1438,7 +1450,7 @@ var DropClawGateway = class {
|
|
|
1438
1450
|
}
|
|
1439
1451
|
}
|
|
1440
1452
|
this.running = true;
|
|
1441
|
-
log11.info("
|
|
1453
|
+
log11.info("DSClaw Gateway started");
|
|
1442
1454
|
}
|
|
1443
1455
|
async handleMessage(channel, message) {
|
|
1444
1456
|
await runWithTrace(
|
|
@@ -1478,6 +1490,15 @@ var DropClawGateway = class {
|
|
|
1478
1490
|
}
|
|
1479
1491
|
);
|
|
1480
1492
|
}
|
|
1493
|
+
// ─── Auto-welcome on WebSocket connect ──────────────────────────
|
|
1494
|
+
handleWebConnect(channel, userId) {
|
|
1495
|
+
const session = loadSession(userId);
|
|
1496
|
+
if (session.state === "new") {
|
|
1497
|
+
this.onboardWelcome(channel, userId, session).catch((err) => {
|
|
1498
|
+
log11.error({ error: err, userId }, "Auto-welcome failed");
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1481
1502
|
// ─── Onboarding State Machine ─────────────────────────────────────
|
|
1482
1503
|
async handleOnboarding(channel, message, session) {
|
|
1483
1504
|
const userId = message.userId;
|
|
@@ -1501,7 +1522,7 @@ var DropClawGateway = class {
|
|
|
1501
1522
|
}
|
|
1502
1523
|
}
|
|
1503
1524
|
async onboardWelcome(channel, userId, session) {
|
|
1504
|
-
const welcome = "Welcome to *
|
|
1525
|
+
const welcome = "Welcome to *DSClaw*! I'm your AI assistant for dropshipping.\n\nI can help you:\n- Import products from AliExpress, Temu, 1688\n- Push products to your Shopify/WooCommerce store\n- Check inventory, pricing rules, and orders\n- ...all through this chat!\n\nLet's get you set up. It takes about 1 minute.\n\n*Step 1/3:* What is your DSers account email?";
|
|
1505
1526
|
await channel.send(userId, { text: welcome });
|
|
1506
1527
|
session.state = "onboard_dsers_email";
|
|
1507
1528
|
saveSession(userId, session);
|
|
@@ -1553,11 +1574,11 @@ var DropClawGateway = class {
|
|
|
1553
1574
|
await channel.send(userId, {
|
|
1554
1575
|
card: {
|
|
1555
1576
|
title: "DSers Connected!",
|
|
1556
|
-
summary: "Your DSers account is verified.\n\n*Step 3/3:* Choose your AI provider.\
|
|
1577
|
+
summary: "Your DSers account is verified.\n\n*Step 3/3:* Choose your AI provider and paste your API key.\nYou only need a key \u2014 DSClaw handles everything else.\n\nGet a free key here:\n- **OpenAI**: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)\n- **Anthropic**: [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys)\n- **Google** (recommended, free tier): [aistudio.google.com/apikey](https://aistudio.google.com/apikey)",
|
|
1557
1578
|
actions: [
|
|
1558
1579
|
{ label: "OpenAI (GPT-4o)", callbackData: "llm:openai" },
|
|
1559
1580
|
{ label: "Anthropic (Claude)", callbackData: "llm:anthropic" },
|
|
1560
|
-
{ label: "Google (Gemini)", callbackData: "llm:google" }
|
|
1581
|
+
{ label: "Google (Gemini) \u2605", callbackData: "llm:google" }
|
|
1561
1582
|
]
|
|
1562
1583
|
}
|
|
1563
1584
|
});
|
|
@@ -1588,11 +1609,11 @@ var DropClawGateway = class {
|
|
|
1588
1609
|
await channel.send(userId, {
|
|
1589
1610
|
card: {
|
|
1590
1611
|
title: "Choose AI Provider",
|
|
1591
|
-
summary: "Please select your AI provider first:",
|
|
1612
|
+
summary: "Please select your AI provider first.\n\nGet a free key:\n- **OpenAI**: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)\n- **Anthropic**: [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys)\n- **Google** (recommended): [aistudio.google.com/apikey](https://aistudio.google.com/apikey)",
|
|
1592
1613
|
actions: [
|
|
1593
1614
|
{ label: "OpenAI (GPT-4o)", callbackData: "llm:openai" },
|
|
1594
1615
|
{ label: "Anthropic (Claude)", callbackData: "llm:anthropic" },
|
|
1595
|
-
{ label: "Google (Gemini)", callbackData: "llm:google" }
|
|
1616
|
+
{ label: "Google (Gemini) \u2605", callbackData: "llm:google" }
|
|
1596
1617
|
]
|
|
1597
1618
|
}
|
|
1598
1619
|
});
|
|
@@ -1617,7 +1638,7 @@ var DropClawGateway = class {
|
|
|
1617
1638
|
session.state = "ready";
|
|
1618
1639
|
saveSession(userId, session);
|
|
1619
1640
|
await channel.send(userId, {
|
|
1620
|
-
text: `All set!
|
|
1641
|
+
text: `All set! DSClaw is ready.
|
|
1621
1642
|
|
|
1622
1643
|
Try these:
|
|
1623
1644
|
- "Show me my stores"
|
|
@@ -1751,17 +1772,17 @@ Just type naturally \u2014 I understand everyday language!`
|
|
|
1751
1772
|
apiKey: session.llmApiKey,
|
|
1752
1773
|
model: session.llmModel ?? getDefaultModel(session.llmProvider)
|
|
1753
1774
|
};
|
|
1754
|
-
agent = new
|
|
1775
|
+
agent = new DSClawCoreAgent(dsersClient, this.memory, llm);
|
|
1755
1776
|
this.agents.set(userId, agent);
|
|
1756
1777
|
return agent;
|
|
1757
1778
|
}
|
|
1758
1779
|
async stop() {
|
|
1759
|
-
log11.info("Stopping
|
|
1780
|
+
log11.info("Stopping DSClaw Gateway...");
|
|
1760
1781
|
for (const ch of this.channels) {
|
|
1761
1782
|
await ch.disconnect();
|
|
1762
1783
|
}
|
|
1763
1784
|
this.running = false;
|
|
1764
|
-
log11.info("
|
|
1785
|
+
log11.info("DSClaw Gateway stopped");
|
|
1765
1786
|
}
|
|
1766
1787
|
isRunning() {
|
|
1767
1788
|
return this.running;
|
|
@@ -1780,18 +1801,18 @@ async function startCommand(opts) {
|
|
|
1780
1801
|
try {
|
|
1781
1802
|
const config = loadConfig(opts.config);
|
|
1782
1803
|
const url = `http://localhost:${config.port}`;
|
|
1783
|
-
console.log("\n
|
|
1804
|
+
console.log("\n DSClaw");
|
|
1784
1805
|
console.log(` Web Chat: ${url}`);
|
|
1785
1806
|
if (config.telegramBotToken) {
|
|
1786
1807
|
console.log(" Telegram: enabled");
|
|
1787
1808
|
}
|
|
1788
1809
|
console.log();
|
|
1789
|
-
const gateway = new
|
|
1810
|
+
const gateway = new DSClawGateway(config);
|
|
1790
1811
|
await gateway.start();
|
|
1791
1812
|
if (opts.open !== false) {
|
|
1792
1813
|
await openBrowser(url);
|
|
1793
1814
|
}
|
|
1794
|
-
console.log(`
|
|
1815
|
+
console.log(` DSClaw is running \u2014 chat at ${url}
|
|
1795
1816
|
`);
|
|
1796
1817
|
const shutdown = async () => {
|
|
1797
1818
|
log12.info("Shutting down gracefully...");
|
|
@@ -1812,7 +1833,7 @@ async function startCommand(opts) {
|
|
|
1812
1833
|
|
|
1813
1834
|
// src/cli/doctor.ts
|
|
1814
1835
|
async function doctorCommand() {
|
|
1815
|
-
console.log("\n
|
|
1836
|
+
console.log("\n DSClaw Doctor\n");
|
|
1816
1837
|
const results = [];
|
|
1817
1838
|
if (configExists()) {
|
|
1818
1839
|
try {
|
|
@@ -1842,7 +1863,7 @@ async function doctorCommand() {
|
|
|
1842
1863
|
results.push({
|
|
1843
1864
|
name: "Config file",
|
|
1844
1865
|
status: "fail",
|
|
1845
|
-
message: "Not found. Run '
|
|
1866
|
+
message: "Not found. Run 'dsclaw init' first."
|
|
1846
1867
|
});
|
|
1847
1868
|
}
|
|
1848
1869
|
try {
|
|
@@ -1879,8 +1900,8 @@ var program = new Command();
|
|
|
1879
1900
|
program.name("dsclaw").description("AI-powered dropshipping agent \u2014 chat with your DSers store.").version("0.1.0");
|
|
1880
1901
|
program.action(() => startCommand({}));
|
|
1881
1902
|
program.command("init").description("Setup wizard \u2014 configure Telegram (optional)").action(initCommand);
|
|
1882
|
-
program.command("start").description("Start the
|
|
1883
|
-
program.command("stop").description("Stop the
|
|
1903
|
+
program.command("start").description("Start the DSClaw bot").option("-c, --config <path>", "Path to config file").option("--no-open", "Don't auto-open browser").action(startCommand);
|
|
1904
|
+
program.command("stop").description("Stop the DSClaw bot (PM2)").action(async () => {
|
|
1884
1905
|
const { execSync } = await import("child_process");
|
|
1885
1906
|
try {
|
|
1886
1907
|
execSync("pm2 stop dsclaw", { stdio: "inherit" });
|