instar 0.7.15 → 0.7.17

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.
@@ -651,21 +651,29 @@ The greeting should be **in the agent's voice** AND explain how Telegram topics
651
651
 
652
652
  Adapt the tone and examples to the agent's personality and role. Keep it warm and practical.
653
653
 
654
- ### Step 5c: Tell the User
654
+ ### Step 5c: Install Auto-Start
655
655
 
656
- After the server is running and the greeting is sent:
656
+ After the server starts, install auto-start so the agent comes back on login:
657
657
 
658
- > "All done! [Agent name] just messaged you in the Lifeline topic on Telegram. From here on, that's your primary channel — just talk to your agent there."
658
+ ```bash
659
+ npx instar autostart install --dir <project_dir>
660
+ ```
659
661
 
660
- Then explain the connectivity requirement clearly:
662
+ This creates a macOS LaunchAgent or Linux systemd service. The agent will start automatically whenever the user logs in — nothing to remember.
661
663
 
662
- > "One important thing to know: your agent runs on this computer. As long as it's on and awake, your agent is reachable via Telegram."
663
- >
664
- > "If your computer goes to sleep or shuts down, Telegram messages will queue up. Your agent will pick them up when it wakes back up."
664
+ ### Step 5d: Tell the User
665
+
666
+ After the server is running, auto-start is installed, and the greeting is sent:
667
+
668
+ > "All done! [Agent name] just messaged you in the Lifeline topic on Telegram. From here on, that's your primary channel — just talk to your agent there."
665
669
  >
666
- > "For always-on access, some users keep a dedicated machine running but it's not required to get started."
670
+ > "I've set up auto-start your agent will come back automatically when you log in. As long as your computer is on and awake, Telegram just works."
671
+
672
+ If auto-start install failed, explain the fallback:
673
+
674
+ > "Your agent runs on this computer. If your computer restarts, you'll need to run `instar server start` to bring it back."
667
675
 
668
- Keep it matter-of-fact, not alarming. The user should understand the tradeoff without feeling like they need a server farm to use this.
676
+ Keep it matter-of-fact, not alarming.
669
677
 
670
678
  **Do NOT present a list of CLI commands or next steps.** The setup wizard's job is done. The user's next action is opening Telegram and replying to their agent.
671
679
 
package/dist/cli.js CHANGED
@@ -547,5 +547,82 @@ program
547
547
  console.log();
548
548
  }
549
549
  });
550
+ // ── Auto-Start ───────────────────────────────────────────────────
551
+ const autostartCmd = program
552
+ .command('autostart')
553
+ .description('Manage auto-start on login (agent starts when you log into your computer)');
554
+ autostartCmd
555
+ .command('install')
556
+ .description('Install auto-start so your agent starts on login')
557
+ .option('-d, --dir <path>', 'Project directory')
558
+ .action(async (opts) => {
559
+ const { loadConfig } = await import('./core/Config.js');
560
+ const { installAutoStart } = await import('./commands/setup.js');
561
+ const config = loadConfig(opts.dir);
562
+ const hasTelegram = config.messaging?.some((m) => m.type === 'telegram') ?? false;
563
+ const installed = installAutoStart(config.projectName, config.projectDir, hasTelegram);
564
+ if (installed) {
565
+ console.log(pc.green(`Auto-start installed for "${config.projectName}".`));
566
+ console.log(pc.dim('Your agent will start automatically when you log in.'));
567
+ }
568
+ else {
569
+ console.log(pc.red('Failed to install auto-start.'));
570
+ console.log(pc.dim(`Platform: ${process.platform} — auto-start supports macOS and Linux.`));
571
+ }
572
+ });
573
+ autostartCmd
574
+ .command('uninstall')
575
+ .description('Remove auto-start')
576
+ .option('-d, --dir <path>', 'Project directory')
577
+ .action(async (opts) => {
578
+ const { loadConfig } = await import('./core/Config.js');
579
+ const { uninstallAutoStart } = await import('./commands/setup.js');
580
+ const config = loadConfig(opts.dir);
581
+ const removed = uninstallAutoStart(config.projectName);
582
+ if (removed) {
583
+ console.log(pc.green(`Auto-start removed for "${config.projectName}".`));
584
+ }
585
+ else {
586
+ console.log(pc.yellow('No auto-start found to remove.'));
587
+ }
588
+ });
589
+ autostartCmd
590
+ .command('status')
591
+ .description('Check if auto-start is installed')
592
+ .option('-d, --dir <path>', 'Project directory')
593
+ .action(async (opts) => {
594
+ const { loadConfig } = await import('./core/Config.js');
595
+ const config = loadConfig(opts.dir);
596
+ const os = await import('node:os');
597
+ const fs = await import('node:fs');
598
+ const path = await import('node:path');
599
+ if (process.platform === 'darwin') {
600
+ const label = `ai.instar.${config.projectName}`;
601
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
602
+ if (fs.existsSync(plistPath)) {
603
+ console.log(pc.green(`Auto-start is installed (macOS LaunchAgent: ${label})`));
604
+ console.log(pc.dim(` Plist: ${plistPath}`));
605
+ }
606
+ else {
607
+ console.log(pc.yellow('Auto-start is not installed.'));
608
+ console.log(pc.dim(' Install with: instar autostart install'));
609
+ }
610
+ }
611
+ else if (process.platform === 'linux') {
612
+ const serviceName = `instar-${config.projectName}.service`;
613
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', serviceName);
614
+ if (fs.existsSync(servicePath)) {
615
+ console.log(pc.green(`Auto-start is installed (systemd user service: ${serviceName})`));
616
+ console.log(pc.dim(` Service: ${servicePath}`));
617
+ }
618
+ else {
619
+ console.log(pc.yellow('Auto-start is not installed.'));
620
+ console.log(pc.dim(' Install with: instar autostart install'));
621
+ }
622
+ }
623
+ else {
624
+ console.log(pc.yellow(`Auto-start is not supported on ${process.platform}.`));
625
+ }
626
+ });
550
627
  program.parse();
551
628
  //# sourceMappingURL=cli.js.map
@@ -21,4 +21,16 @@
21
21
  export declare function runSetup(opts?: {
22
22
  classic?: boolean;
23
23
  }): Promise<void>;
24
+ /**
25
+ * Install auto-start so the agent's lifeline process starts on login.
26
+ * macOS: LaunchAgent plist in ~/Library/LaunchAgents/
27
+ * Linux: systemd user service in ~/.config/systemd/user/
28
+ *
29
+ * Returns true if auto-start was installed successfully.
30
+ */
31
+ export declare function installAutoStart(projectName: string, projectDir: string, hasTelegram: boolean): boolean;
32
+ /**
33
+ * Remove auto-start for a project.
34
+ */
35
+ export declare function uninstallAutoStart(projectName: string): boolean;
24
36
  //# sourceMappingURL=setup.d.ts.map
@@ -17,6 +17,7 @@
17
17
  import { execFileSync, spawn } from 'node:child_process';
18
18
  import { randomUUID } from 'node:crypto';
19
19
  import fs from 'node:fs';
20
+ import os from 'node:os';
20
21
  import path from 'node:path';
21
22
  import pc from 'picocolors';
22
23
  import { input, confirm, select, number } from '@inquirer/prompts';
@@ -449,6 +450,12 @@ async function runClassicSetup() {
449
450
  console.log(pc.dim(' Starting server...'));
450
451
  const { startServer } = await import('./server.js');
451
452
  await startServer({ foreground: false });
453
+ // ── Auto-start on login ──────────────────────────────────────────
454
+ const hasTelegram = !!telegramConfig?.chatId;
455
+ const autoStartInstalled = installAutoStart(projectName, projectDir, hasTelegram);
456
+ if (autoStartInstalled) {
457
+ console.log(pc.green(' ✓ Auto-start installed — your agent will start on login.'));
458
+ }
452
459
  if (telegramConfig?.chatId) {
453
460
  // Create the Lifeline topic — the always-available channel
454
461
  let lifelineThreadId = null;
@@ -520,9 +527,15 @@ async function runClassicSetup() {
520
527
  console.log(pc.bold(` All done! ${projectName} just messaged you${topicNote} on Telegram.`));
521
528
  console.log(pc.dim(' That\'s your primary channel from here on — no terminal needed.'));
522
529
  console.log();
523
- console.log(pc.dim(' Your agent runs on this computer. As long as it\'s on and awake,'));
524
- console.log(pc.dim(' your agent is reachable via Telegram. If your computer sleeps or'));
525
- console.log(pc.dim(' shuts down, messages will queue and be picked up when it wakes.'));
530
+ if (autoStartInstalled) {
531
+ console.log(pc.dim(' Your agent starts automatically when you log in nothing to remember.'));
532
+ console.log(pc.dim(' As long as your computer is on and awake, Telegram just works.'));
533
+ }
534
+ else {
535
+ console.log(pc.dim(' Your agent runs on this computer. As long as it\'s on and awake,'));
536
+ console.log(pc.dim(' your agent is reachable via Telegram. You\'ll need to run'));
537
+ console.log(pc.dim(` ${pc.cyan('instar server start')} after a reboot.`));
538
+ }
526
539
  }
527
540
  else {
528
541
  console.log();
@@ -588,6 +601,208 @@ function isInstarGlobal() {
588
601
  return false;
589
602
  }
590
603
  }
604
+ // ── Auto-Start on Login ─────────────────────────────────────────
605
+ /**
606
+ * Install auto-start so the agent's lifeline process starts on login.
607
+ * macOS: LaunchAgent plist in ~/Library/LaunchAgents/
608
+ * Linux: systemd user service in ~/.config/systemd/user/
609
+ *
610
+ * Returns true if auto-start was installed successfully.
611
+ */
612
+ export function installAutoStart(projectName, projectDir, hasTelegram) {
613
+ const platform = process.platform;
614
+ if (platform === 'darwin') {
615
+ return installMacOSLaunchAgent(projectName, projectDir, hasTelegram);
616
+ }
617
+ else if (platform === 'linux') {
618
+ return installLinuxSystemdService(projectName, projectDir, hasTelegram);
619
+ }
620
+ else {
621
+ // Windows or other — no auto-start support yet
622
+ return false;
623
+ }
624
+ }
625
+ /**
626
+ * Remove auto-start for a project.
627
+ */
628
+ export function uninstallAutoStart(projectName) {
629
+ const platform = process.platform;
630
+ if (platform === 'darwin') {
631
+ const label = `ai.instar.${projectName}`;
632
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${label}.plist`);
633
+ // Unload if loaded
634
+ try {
635
+ execFileSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, plistPath], { stdio: 'ignore' });
636
+ }
637
+ catch { /* not loaded */ }
638
+ // Remove file
639
+ try {
640
+ fs.unlinkSync(plistPath);
641
+ return true;
642
+ }
643
+ catch {
644
+ return false;
645
+ }
646
+ }
647
+ else if (platform === 'linux') {
648
+ const serviceName = `instar-${projectName}.service`;
649
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', serviceName);
650
+ try {
651
+ execFileSync('systemctl', ['--user', 'disable', serviceName], { stdio: 'ignore' });
652
+ execFileSync('systemctl', ['--user', 'stop', serviceName], { stdio: 'ignore' });
653
+ }
654
+ catch { /* not loaded */ }
655
+ try {
656
+ fs.unlinkSync(servicePath);
657
+ execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'ignore' });
658
+ return true;
659
+ }
660
+ catch {
661
+ return false;
662
+ }
663
+ }
664
+ return false;
665
+ }
666
+ function findNodePath() {
667
+ try {
668
+ return execFileSync('which', ['node'], {
669
+ encoding: 'utf-8',
670
+ stdio: ['pipe', 'pipe', 'pipe'],
671
+ }).trim();
672
+ }
673
+ catch {
674
+ return '/usr/local/bin/node';
675
+ }
676
+ }
677
+ function findInstarCli() {
678
+ // Find the actual instar CLI entry point
679
+ try {
680
+ const globalPath = execFileSync('which', ['instar'], {
681
+ encoding: 'utf-8',
682
+ stdio: ['pipe', 'pipe', 'pipe'],
683
+ }).trim();
684
+ if (globalPath && !globalPath.includes('.npm/_npx')) {
685
+ return globalPath;
686
+ }
687
+ }
688
+ catch { /* not global */ }
689
+ // Fallback: use the dist/cli.js from the npm package
690
+ const cliPath = new URL('../cli.js', import.meta.url).pathname;
691
+ if (fs.existsSync(cliPath)) {
692
+ return cliPath;
693
+ }
694
+ return 'instar';
695
+ }
696
+ function installMacOSLaunchAgent(projectName, projectDir, hasTelegram) {
697
+ const label = `ai.instar.${projectName}`;
698
+ const launchAgentsDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
699
+ const plistPath = path.join(launchAgentsDir, `${label}.plist`);
700
+ const logDir = path.join(projectDir, '.instar', 'logs');
701
+ const nodePath = findNodePath();
702
+ const instarCli = findInstarCli();
703
+ // Determine what to start: lifeline if Telegram configured, otherwise just the server
704
+ const command = hasTelegram ? 'lifeline' : 'server';
705
+ const args = hasTelegram
706
+ ? [instarCli, 'lifeline', 'start', '--dir', projectDir]
707
+ : [instarCli, 'server', 'start', '--foreground', '--dir', projectDir];
708
+ // If instar CLI is a node script (not a binary), prepend node
709
+ const isNodeScript = instarCli.endsWith('.js') || instarCli.endsWith('.mjs');
710
+ const programArgs = isNodeScript ? [nodePath, ...args] : args;
711
+ // Build the plist XML
712
+ const argsXml = programArgs.map(a => ` <string>${escapeXml(a)}</string>`).join('\n');
713
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
714
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
715
+ <plist version="1.0">
716
+ <dict>
717
+ <key>Label</key>
718
+ <string>${escapeXml(label)}</string>
719
+ <key>ProgramArguments</key>
720
+ <array>
721
+ ${argsXml}
722
+ </array>
723
+ <key>WorkingDirectory</key>
724
+ <string>${escapeXml(projectDir)}</string>
725
+ <key>RunAtLoad</key>
726
+ <true/>
727
+ <key>KeepAlive</key>
728
+ <true/>
729
+ <key>StandardOutPath</key>
730
+ <string>${escapeXml(path.join(logDir, `${command}-launchd.log`))}</string>
731
+ <key>StandardErrorPath</key>
732
+ <string>${escapeXml(path.join(logDir, `${command}-launchd.err`))}</string>
733
+ <key>EnvironmentVariables</key>
734
+ <dict>
735
+ <key>PATH</key>
736
+ <string>${escapeXml(process.env.PATH || '/usr/local/bin:/usr/bin:/bin')}</string>
737
+ </dict>
738
+ <key>ThrottleInterval</key>
739
+ <integer>10</integer>
740
+ </dict>
741
+ </plist>`;
742
+ try {
743
+ fs.mkdirSync(launchAgentsDir, { recursive: true });
744
+ fs.mkdirSync(logDir, { recursive: true });
745
+ fs.writeFileSync(plistPath, plist);
746
+ // Load the agent
747
+ try {
748
+ // Unload first if already loaded
749
+ execFileSync('launchctl', ['bootout', `gui/${process.getuid?.() ?? 501}`, plistPath], { stdio: 'ignore' });
750
+ }
751
+ catch { /* not loaded yet — fine */ }
752
+ execFileSync('launchctl', ['bootstrap', `gui/${process.getuid?.() ?? 501}`, plistPath], { stdio: 'ignore' });
753
+ return true;
754
+ }
755
+ catch {
756
+ return false;
757
+ }
758
+ }
759
+ function installLinuxSystemdService(projectName, projectDir, hasTelegram) {
760
+ const serviceName = `instar-${projectName}.service`;
761
+ const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
762
+ const servicePath = path.join(serviceDir, serviceName);
763
+ const nodePath = findNodePath();
764
+ const instarCli = findInstarCli();
765
+ const command = hasTelegram ? 'lifeline' : 'server';
766
+ const args = hasTelegram
767
+ ? `${instarCli} lifeline start --dir ${projectDir}`
768
+ : `${instarCli} server start --foreground --dir ${projectDir}`;
769
+ const isNodeScript = instarCli.endsWith('.js') || instarCli.endsWith('.mjs');
770
+ const execStart = isNodeScript ? `${nodePath} ${args}` : args;
771
+ const service = `[Unit]
772
+ Description=Instar Agent - ${projectName}
773
+ After=network.target
774
+
775
+ [Service]
776
+ Type=simple
777
+ ExecStart=${execStart}
778
+ WorkingDirectory=${projectDir}
779
+ Restart=always
780
+ RestartSec=10
781
+ Environment=PATH=${process.env.PATH || '/usr/local/bin:/usr/bin:/bin'}
782
+
783
+ [Install]
784
+ WantedBy=default.target
785
+ `;
786
+ try {
787
+ fs.mkdirSync(serviceDir, { recursive: true });
788
+ fs.writeFileSync(servicePath, service);
789
+ execFileSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'ignore' });
790
+ execFileSync('systemctl', ['--user', 'enable', serviceName], { stdio: 'ignore' });
791
+ execFileSync('systemctl', ['--user', 'start', serviceName], { stdio: 'ignore' });
792
+ return true;
793
+ }
794
+ catch {
795
+ return false;
796
+ }
797
+ }
798
+ function escapeXml(str) {
799
+ return str
800
+ .replace(/&/g, '&amp;')
801
+ .replace(/</g, '&lt;')
802
+ .replace(/>/g, '&gt;')
803
+ .replace(/"/g, '&quot;')
804
+ .replace(/'/g, '&apos;');
805
+ }
591
806
  // ── Prompt Helpers ───────────────────────────────────────────────
592
807
  /**
593
808
  * Full Telegram walkthrough. Returns config or null if skipped.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "0.7.15",
3
+ "version": "0.7.17",
4
4
  "description": "Persistent autonomy infrastructure for AI agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,11 +0,0 @@
1
- > Why do I have a folder named ".vercel" in my project?
2
- The ".vercel" folder is created when you link a directory to a Vercel project.
3
-
4
- > What does the "project.json" file contain?
5
- The "project.json" file contains:
6
- - The ID of the Vercel project that you linked ("projectId")
7
- - The ID of the user or team your Vercel project is owned by ("orgId")
8
-
9
- > Should I commit the ".vercel" folder?
10
- No, you should not share the ".vercel" folder with anyone.
11
- Upon creation, it will be automatically added to your ".gitignore" file.
@@ -1 +0,0 @@
1
- {"projectId":"prj_evM5LcItYL3IAmw8zNvEPGrHeaya","orgId":"team_dHctwIDcV3X9ydapQlCPHFGI","projectName":"claude-agent-kit"}