heyio 1.2.2 → 1.2.4

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.
@@ -1,7 +1,9 @@
1
1
  import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join, basename } from "node:path";
3
- import { execSync } from "node:child_process";
3
+ import { exec } from "node:child_process";
4
+ import { promisify } from "node:util";
4
5
  import { PATHS } from "../paths.js";
6
+ const execAsync = promisify(exec);
5
7
  export async function listSkills() {
6
8
  if (!existsSync(PATHS.skills))
7
9
  return [];
@@ -35,7 +37,7 @@ export async function addSkill(url) {
35
37
  if (existsSync(dest)) {
36
38
  throw new Error(`Skill "${slug}" is already installed.`);
37
39
  }
38
- execSync(`git clone --depth 1 ${url} ${dest}`, { stdio: "pipe" });
40
+ await execAsync(`git clone --depth 1 ${url} ${dest}`);
39
41
  // Verify SKILL.md exists
40
42
  if (!existsSync(join(dest, "SKILL.md"))) {
41
43
  rmSync(dest, { recursive: true, force: true });
@@ -31,6 +31,18 @@ You are IO, a personal AI assistant daemon. You run 24/7 on the user's machine,
31
31
  - If the user says "do this" for a complex task → use squad_meeting with execute_after=true
32
32
  - If the user says "just do it" or it's a simple task → use squad_delegate directly
33
33
 
34
+ ## HARD RULE: Squad Ownership Boundary
35
+ If a project has a squad assigned to it, you (the orchestrator) must NEVER:
36
+ - Research, analyze, or investigate the project's code, issues, or state yourself
37
+ - Attempt any work — even preliminary analysis — before delegating
38
+ - "Look into" or "check on" something before passing it to the squad
39
+
40
+ When a request comes in about a squad-owned project, you IMMEDIATELY delegate to that squad's team lead with no pre-processing. The squad handles ALL work including research, analysis, planning, and execution.
41
+
42
+ The ONLY thing you are allowed to do regarding a squad-owned project (without delegating) is:
43
+ - Answer questions about what the squad has already done (using feed/task history)
44
+ - Report squad status, task progress, or past deliverables
45
+
34
46
  ## Squad Coverage Requirements
35
47
  Every squad MUST have:
36
48
  1. A dedicated team lead (PM/Senior Engineer, coordination-only — **never writes code**)
@@ -69,10 +69,12 @@ export function createTools() {
69
69
  const squad = createSquad(name, universe, repo_url);
70
70
  let cloneMsg = "";
71
71
  if (repo_url) {
72
- const { execSync } = await import("node:child_process");
72
+ const { exec } = await import("node:child_process");
73
+ const { promisify } = await import("node:util");
73
74
  const { existsSync, mkdirSync } = await import("node:fs");
74
75
  const { join } = await import("node:path");
75
76
  const { PATHS } = await import("../paths.js");
77
+ const execAsync = promisify(exec);
76
78
  // Extract owner/repo from URL (supports https and git@ formats)
77
79
  const match = repo_url.match(/[/:]([^/]+)\/([^/.]+?)(?:\.git)?$/);
78
80
  if (match) {
@@ -83,8 +85,7 @@ export function createTools() {
83
85
  if (!existsSync(parentDir))
84
86
  mkdirSync(parentDir, { recursive: true });
85
87
  try {
86
- execSync(`git clone ${repo_url} ${sourceDir}`, {
87
- encoding: "utf-8",
88
+ await execAsync(`git clone ${repo_url} ${sourceDir}`, {
88
89
  timeout: 120_000,
89
90
  });
90
91
  cloneMsg = ` Repo cloned to ~/.io/source/${owner}/${repo}.`;
@@ -313,22 +314,23 @@ export function createTools() {
313
314
  cwd: z.string().optional().describe("Working directory (defaults to home directory)"),
314
315
  }),
315
316
  handler: async ({ command, cwd }) => {
316
- const { execSync } = await import("node:child_process");
317
+ const { exec } = await import("node:child_process");
318
+ const { promisify } = await import("node:util");
317
319
  const { homedir } = await import("node:os");
320
+ const execAsync = promisify(exec);
318
321
  try {
319
- const output = execSync(command, {
322
+ const { stdout } = await execAsync(command, {
320
323
  cwd: cwd ?? homedir(),
321
- encoding: "utf-8",
322
324
  timeout: 60_000,
323
325
  maxBuffer: 1024 * 1024,
324
326
  env: { ...process.env, GH_PROMPT_DISABLED: "1" },
325
327
  });
326
- return output.trim() || "(no output)";
328
+ return stdout.trim() || "(no output)";
327
329
  }
328
330
  catch (err) {
329
331
  const stderr = err.stderr?.toString().trim() ?? "";
330
332
  const stdout = err.stdout?.toString().trim() ?? "";
331
- return `Error (exit ${err.status}): ${stderr || stdout || err.message}`;
333
+ return `Error (exit ${err.code}): ${stderr || stdout || err.message}`;
332
334
  }
333
335
  },
334
336
  }),
@@ -1,6 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { execSync } from "node:child_process";
2
+ import { exec } from "node:child_process";
3
+ import { promisify } from "node:util";
3
4
  import { getDb } from "./db.js";
5
+ const execAsync = promisify(exec);
4
6
  const MAX_INSTANCES_PER_SQUAD = 3;
5
7
  export async function createInstance(squadId, branch) {
6
8
  const db = getDb();
@@ -19,18 +21,13 @@ export async function createInstance(squadId, branch) {
19
21
  const id = randomUUID();
20
22
  const worktreePath = `/tmp/io-worktrees/${squadId}/${branch}`;
21
23
  // Create git worktree
24
+ const repoCwd = squad.repo_url.startsWith("/") ? squad.repo_url : process.cwd();
22
25
  try {
23
- execSync(`git worktree add ${worktreePath} -b ${branch}`, {
24
- cwd: squad.repo_url.startsWith("/") ? squad.repo_url : process.cwd(),
25
- stdio: "pipe",
26
- });
26
+ await execAsync(`git worktree add ${worktreePath} -b ${branch}`, { cwd: repoCwd });
27
27
  }
28
- catch (err) {
28
+ catch {
29
29
  // Branch may already exist
30
- execSync(`git worktree add ${worktreePath} ${branch}`, {
31
- cwd: squad.repo_url.startsWith("/") ? squad.repo_url : process.cwd(),
32
- stdio: "pipe",
33
- });
30
+ await execAsync(`git worktree add ${worktreePath} ${branch}`, { cwd: repoCwd });
34
31
  }
35
32
  db.prepare(`INSERT INTO instances (id, squad_id, branch, worktree_path, status)
36
33
  VALUES (?, ?, ?, ?, 'active')`).run(id, squadId, branch, worktreePath);
@@ -45,9 +42,7 @@ export async function destroyInstance(instanceId) {
45
42
  throw new Error(`Instance ${instanceId} not found.`);
46
43
  // Remove worktree
47
44
  try {
48
- execSync(`git worktree remove ${instance.worktree_path} --force`, {
49
- stdio: "pipe",
50
- });
45
+ await execAsync(`git worktree remove ${instance.worktree_path} --force`);
51
46
  }
52
47
  catch {
53
48
  // Already removed or doesn't exist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyio",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "IO — a personal AI assistant daemon built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "io": "dist/index.js"