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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # SkimpyClaw 👙🦞
2
2
 
3
- Lightweight personal AI assistant (~20k LOC). Runs locally. Telegram/Discord chat, scheduled routines, a web dashboard, and a tool-enabled agent — all in one tiny service.
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 (~20k LOC) | OpenClaw (~700k LOC) |
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(workspaceDir) {
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__', workspaceDir)
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
- const workspaceDir = process.cwd();
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 workspace.
533
- renderGatewayPlist(workspaceDir);
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('👙🦞 SkimpyClaw Setup')} ${c.dim('(reconfigure — press Enter to keep current values)')}\n`);
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('👙🦞 SkimpyClaw Setup')}\n`);
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(workspaceDir);
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.0",
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",