skimpyclaw 0.1.0 → 0.1.2
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 +2 -2
- package/com.skimpyclaw.gateway.plist.example +32 -0
- package/dist/setup.js +15 -10
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# SkimpyClaw 👙🦞
|
|
2
2
|
|
|
3
|
-
Lightweight personal AI assistant (~
|
|
3
|
+
Lightweight personal AI assistant (~23k LOC). Runs locally. Telegram/Discord chat, scheduled routines, a web dashboard, and a tool-enabled agent — all in one tiny service.
|
|
4
4
|
|
|
5
5
|
## Why SkimpyClaw vs OpenClaw
|
|
6
6
|
|
|
7
7
|
Both are personal AI assistants you run yourself. The difference is scope.
|
|
8
8
|
|
|
9
|
-
| | SkimpyClaw (~
|
|
9
|
+
| | SkimpyClaw (~23k LOC) | OpenClaw (~700k LOC) |
|
|
10
10
|
| ------------------- | --------------------------------------- | -------------------------------------------------------- |
|
|
11
11
|
| **Channels** | Telegram, Discord | WhatsApp, Signal, iMessage, Slack, Teams, Matrix, + more |
|
|
12
12
|
| **Setup** | `skimpyclaw onboard` → done | Daemon + wizard + per-channel pairing |
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>com.skimpyclaw.gateway</string>
|
|
7
|
+
<key>Comment</key>
|
|
8
|
+
<string>SkimpyClaw personal AI assistant gateway</string>
|
|
9
|
+
<key>RunAtLoad</key>
|
|
10
|
+
<true/>
|
|
11
|
+
<key>KeepAlive</key>
|
|
12
|
+
<true/>
|
|
13
|
+
<key>ProgramArguments</key>
|
|
14
|
+
<array>
|
|
15
|
+
<string>__NODE_BIN__</string>
|
|
16
|
+
<string>__ENTRYPOINT__</string>
|
|
17
|
+
</array>
|
|
18
|
+
<key>WorkingDirectory</key>
|
|
19
|
+
<string>__REPO_DIR__</string>
|
|
20
|
+
<key>EnvironmentVariables</key>
|
|
21
|
+
<dict>
|
|
22
|
+
<key>SKIMPYCLAW_LAUNCHD</key>
|
|
23
|
+
<string>1</string>
|
|
24
|
+
<key>PATH</key>
|
|
25
|
+
<string>__NODE_BIN_DIR__:__PNPM_BIN_DIR__:__SYSTEM_PATH__</string>
|
|
26
|
+
</dict>
|
|
27
|
+
<key>StandardOutPath</key>
|
|
28
|
+
<string>__HOME_DIR__/.skimpyclaw/logs/gateway.stdout.log</string>
|
|
29
|
+
<key>StandardErrorPath</key>
|
|
30
|
+
<string>__HOME_DIR__/.skimpyclaw/logs/gateway.stderr.log</string>
|
|
31
|
+
</dict>
|
|
32
|
+
</plist>
|
package/dist/setup.js
CHANGED
|
@@ -68,7 +68,7 @@ function maskInput(input) {
|
|
|
68
68
|
return '****';
|
|
69
69
|
return input.slice(0, 4) + '****' + input.slice(-4);
|
|
70
70
|
}
|
|
71
|
-
function renderGatewayPlist(
|
|
71
|
+
function renderGatewayPlist() {
|
|
72
72
|
if (!existsSync(GATEWAY_PLIST_TEMPLATE)) {
|
|
73
73
|
throw new Error(`Gateway launchd template not found: ${GATEWAY_PLIST_TEMPLATE}`);
|
|
74
74
|
}
|
|
@@ -77,12 +77,18 @@ function renderGatewayPlist(workspaceDir) {
|
|
|
77
77
|
const homeDir = homedir();
|
|
78
78
|
const pnpmBinDir = process.env.PNPM_HOME || join(homeDir, 'Library', 'pnpm');
|
|
79
79
|
const systemPath = process.env.PATH || '/usr/local/bin:/usr/bin:/bin';
|
|
80
|
+
const packageRoot = join(__dirname, '..');
|
|
81
|
+
const entrypoint = join(packageRoot, 'dist', 'index.js');
|
|
82
|
+
if (!existsSync(entrypoint)) {
|
|
83
|
+
throw new Error(`Gateway entrypoint not found: ${entrypoint}`);
|
|
84
|
+
}
|
|
80
85
|
return readFileSync(GATEWAY_PLIST_TEMPLATE, 'utf-8')
|
|
81
86
|
.replaceAll('__NODE_BIN__', nodeBin)
|
|
87
|
+
.replaceAll('__ENTRYPOINT__', entrypoint)
|
|
82
88
|
.replaceAll('__NODE_BIN_DIR__', nodeBinDir)
|
|
83
89
|
.replaceAll('__PNPM_BIN_DIR__', pnpmBinDir)
|
|
84
90
|
.replaceAll('__SYSTEM_PATH__', systemPath)
|
|
85
|
-
.replaceAll('__REPO_DIR__',
|
|
91
|
+
.replaceAll('__REPO_DIR__', packageRoot)
|
|
86
92
|
.replaceAll('__HOME_DIR__', homeDir);
|
|
87
93
|
}
|
|
88
94
|
const PROVIDER_OPTIONS = [
|
|
@@ -521,7 +527,7 @@ async function validateProviderAuth(providers, secrets) {
|
|
|
521
527
|
export async function runSetup(options = {}) {
|
|
522
528
|
const dryRun = options.dryRun ?? false;
|
|
523
529
|
if (dryRun) {
|
|
524
|
-
|
|
530
|
+
console.log(`\n${c.bold('👙🦞✨ SkimpyClaw Setup')} ${c.dim('(dry run)')}\n`);
|
|
525
531
|
if (!existsSync(TEMPLATES_DIR)) {
|
|
526
532
|
throw new Error(`Templates directory not found: ${TEMPLATES_DIR}`);
|
|
527
533
|
}
|
|
@@ -529,8 +535,8 @@ export async function runSetup(options = {}) {
|
|
|
529
535
|
if (templates.length === 0) {
|
|
530
536
|
throw new Error(`No markdown templates found in ${TEMPLATES_DIR}`);
|
|
531
537
|
}
|
|
532
|
-
// Validate launchd template rendering with current environment and
|
|
533
|
-
renderGatewayPlist(
|
|
538
|
+
// Validate launchd template rendering with current environment and install root.
|
|
539
|
+
renderGatewayPlist();
|
|
534
540
|
console.log('✅ Onboarding dry run successful.');
|
|
535
541
|
console.log(`Would create config under: ${CONFIG_DIR}`);
|
|
536
542
|
console.log(`Would copy ${templates.length} templates to: ${AGENTS_DIR}`);
|
|
@@ -545,10 +551,10 @@ export async function runSetup(options = {}) {
|
|
|
545
551
|
const existing = loadExistingSetup();
|
|
546
552
|
const isReconfigure = existing.config !== null;
|
|
547
553
|
if (isReconfigure) {
|
|
548
|
-
console.log(`\n${c.bold('
|
|
554
|
+
console.log(`\n${c.bold('👙🦞✨ SkimpyClaw Setup')} ${c.dim('(reconfigure — press Enter to keep current values)')}\n`);
|
|
549
555
|
}
|
|
550
556
|
else {
|
|
551
|
-
console.log(`\n${c.bold('
|
|
557
|
+
console.log(`\n${c.bold('👙🦞✨ SkimpyClaw Setup')}\n`);
|
|
552
558
|
}
|
|
553
559
|
// 1. Telegram Bot Token
|
|
554
560
|
const existingTgToken = existing.env.TELEGRAM_BOT_TOKEN || '';
|
|
@@ -671,9 +677,8 @@ export async function runSetup(options = {}) {
|
|
|
671
677
|
voice: enableVoice,
|
|
672
678
|
mcp: enableMcp,
|
|
673
679
|
};
|
|
674
|
-
const workspaceDir = process.cwd();
|
|
675
680
|
const { configJson: rawConfigJson, envContent, config: generatedConfig } = buildSetupArtifacts({
|
|
676
|
-
workspaceDir,
|
|
681
|
+
workspaceDir: process.cwd(),
|
|
677
682
|
telegramId,
|
|
678
683
|
telegramToken,
|
|
679
684
|
discordToken: useDiscord ? discordToken : undefined,
|
|
@@ -764,7 +769,7 @@ export async function runSetup(options = {}) {
|
|
|
764
769
|
writeFileSync(join(AGENTS_DIR, 'USER.md'), `# USER.md - About ${userName}\n\nName: ${userName}\n\n## Preferences\n\n- Direct communication, no fluff\n\n## Routines\n\n- Morning: Review tasks and messages\n- EOD: Review completed work, plan tomorrow\n`);
|
|
765
770
|
// Create launchd plist from template
|
|
766
771
|
const plistPath = join(homedir(), 'Library', 'LaunchAgents', `${GATEWAY_PLIST_LABEL}.plist`);
|
|
767
|
-
const plistContent = renderGatewayPlist(
|
|
772
|
+
const plistContent = renderGatewayPlist();
|
|
768
773
|
mkdirSync(dirname(plistPath), { recursive: true });
|
|
769
774
|
writeFileSync(plistPath, plistContent);
|
|
770
775
|
console.log(`✓ Daemon plist written to ${plistPath}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skimpyclaw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Lightweight personal AI assistant with Telegram and Discord integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"templates",
|
|
14
|
+
"com.skimpyclaw.gateway.plist.example",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
@@ -26,6 +27,8 @@
|
|
|
26
27
|
"setup": "tsx src/setup.ts",
|
|
27
28
|
"onboard": "tsx src/cli.ts onboard",
|
|
28
29
|
"build": "tsc && pnpm dashboard:build",
|
|
30
|
+
"release:check": "pnpm build && pnpm test",
|
|
31
|
+
"release:local": "bash ./scripts/release.sh",
|
|
29
32
|
"lint": "eslint \"src/**/*.ts\"",
|
|
30
33
|
"typecheck": "tsc --noEmit",
|
|
31
34
|
"test": "vitest run",
|