heyio 0.1.17 → 0.1.19
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 +44 -4
- package/dist/copilot/agents.js +2 -0
- package/dist/copilot/system-message.js +1 -0
- package/dist/copilot/tools.js +138 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A personal AI assistant daemon built on the GitHub Copilot SDK. IO runs 24/7 on
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/michaeljolley/io/actions/workflows/ci.yml)
|
|
6
6
|

|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
## ✨ Features
|
|
10
10
|
|
|
@@ -15,13 +15,15 @@ A personal AI assistant daemon built on the GitHub Copilot SDK. IO runs 24/7 on
|
|
|
15
15
|
- **Skills** — modular skill system; install from git repos or the [skills.sh](https://skills.sh) registry
|
|
16
16
|
- **Adaptive Sessions** — infinite sessions with automatic context compaction
|
|
17
17
|
- **Worker Agents** — delegated task execution through specialized agent sessions
|
|
18
|
+
- **GitHub Integration** — create, list, view, and comment on issues and PRs via the `github` tool
|
|
19
|
+
- **Smart Model Routing** — automatically selects the best model for each task based on complexity
|
|
18
20
|
- **Self-Updating** — checks for updates and can apply them automatically
|
|
19
21
|
|
|
20
22
|
## 📋 Prerequisites
|
|
21
23
|
|
|
22
|
-
- **Node.js** >=
|
|
24
|
+
- **Node.js** >= 22
|
|
23
25
|
- **GitHub Copilot subscription** — IO uses the Copilot SDK, which requires an active Copilot license
|
|
24
|
-
- **GitHub CLI** (`gh`) —
|
|
26
|
+
- **GitHub CLI** (`gh`) — required for the `github` tool (issue/PR management). Install from [cli.github.com](https://cli.github.com/) and authenticate with `gh auth login`
|
|
25
27
|
|
|
26
28
|
## 🚀 Quick Start
|
|
27
29
|
|
|
@@ -54,6 +56,33 @@ io --daemon
|
|
|
54
56
|
io --self-edit
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
### Headless Server (systemd)
|
|
60
|
+
|
|
61
|
+
To run IO as a background service on a headless server:
|
|
62
|
+
|
|
63
|
+
1. Authenticate the Copilot SDK: `copilot login`
|
|
64
|
+
2. Authenticate the GitHub CLI: `gh auth login`
|
|
65
|
+
3. Create a systemd service file at `/etc/systemd/system/io.service`:
|
|
66
|
+
|
|
67
|
+
```ini
|
|
68
|
+
[Unit]
|
|
69
|
+
Description=IO Personal Assistant
|
|
70
|
+
After=network-online.target
|
|
71
|
+
Wants=network-online.target
|
|
72
|
+
|
|
73
|
+
[Service]
|
|
74
|
+
Type=simple
|
|
75
|
+
ExecStart=/usr/bin/env io --daemon
|
|
76
|
+
Restart=always
|
|
77
|
+
RestartSec=10
|
|
78
|
+
Environment=NODE_ENV=production
|
|
79
|
+
|
|
80
|
+
[Install]
|
|
81
|
+
WantedBy=multi-user.target
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
4. Enable and start: `systemctl enable --now io`
|
|
85
|
+
|
|
57
86
|
## 💬 CLI Usage
|
|
58
87
|
|
|
59
88
|
| Command | Description |
|
|
@@ -80,7 +109,17 @@ IO stores its configuration at `~/.io/config.json`. The setup wizard (`io setup`
|
|
|
80
109
|
"telegramUserId": 123456789,
|
|
81
110
|
|
|
82
111
|
// Enable self-edit mode by default
|
|
83
|
-
"selfEdit": false
|
|
112
|
+
"selfEdit": false,
|
|
113
|
+
|
|
114
|
+
// Default model for the orchestrator
|
|
115
|
+
"defaultModel": "claude-sonnet-4.6",
|
|
116
|
+
|
|
117
|
+
// Model tiers for squad agents (ranked preference lists)
|
|
118
|
+
"modelTiers": {
|
|
119
|
+
"high": ["claude-opus-4.7", "claude-opus-4.6"],
|
|
120
|
+
"medium": ["claude-sonnet-4.6", "gpt-5.5", "claude-opus-4.5"],
|
|
121
|
+
"low": ["claude-haiku-4.5", "gpt-5.4-mini"]
|
|
122
|
+
}
|
|
84
123
|
}
|
|
85
124
|
```
|
|
86
125
|
|
|
@@ -160,6 +199,7 @@ src/
|
|
|
160
199
|
│ ├── orchestrator.ts # Main session management
|
|
161
200
|
│ ├── agents.ts # Worker agent sessions
|
|
162
201
|
│ ├── tools.ts # Tool definitions
|
|
202
|
+
│ ├── model-router.ts # Complexity-based model selection
|
|
163
203
|
│ ├── skills.ts # Skills loader
|
|
164
204
|
│ └── system-message.ts # System prompt builder
|
|
165
205
|
├── store/
|
package/dist/copilot/agents.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "crypto";
|
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
3
|
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync, } from "fs";
|
|
4
4
|
import { join, dirname, resolve } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
5
6
|
import { defineTool, approveAll } from "@github/copilot-sdk";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
import { getClient } from "./client.js";
|
|
@@ -154,6 +155,7 @@ function buildAgentTools(squadSlug) {
|
|
|
154
155
|
timeout: (timeout_secs ?? 60) * 1000,
|
|
155
156
|
maxBuffer: 1024 * 1024,
|
|
156
157
|
cwd: working_dir,
|
|
158
|
+
env: { ...process.env, HOME: process.env.HOME || homedir() },
|
|
157
159
|
});
|
|
158
160
|
const output = result.trim();
|
|
159
161
|
if (output.length > 8000) {
|
|
@@ -86,6 +86,7 @@ The model is selected automatically. Tell the user which model tier was chosen w
|
|
|
86
86
|
### System
|
|
87
87
|
- \`shell\`: Run a shell command. You have full system access — you can create directories, install packages, clone repos, etc. **Always use this instead of the built-in \`bash\` tool.**
|
|
88
88
|
- \`file_ops\`: Read, write, or list files anywhere on the filesystem. Can create directories automatically.
|
|
89
|
+
- \`github\`: Manage GitHub issues and PRs — create, list, view, comment, close issues; create, list, view, comment on PRs.
|
|
89
90
|
- \`web_fetch\`: (built-in) Fetch a URL and return content.
|
|
90
91
|
|
|
91
92
|
## Guidelines
|
package/dist/copilot/tools.js
CHANGED
|
@@ -3,6 +3,14 @@ import { z } from "zod";
|
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdirSync } from "fs";
|
|
5
5
|
import { join, dirname, resolve } from "path";
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
// Ensure child processes have HOME set (systemd services often don't)
|
|
8
|
+
function shellEnv() {
|
|
9
|
+
const env = { ...process.env };
|
|
10
|
+
if (!env.HOME)
|
|
11
|
+
env.HOME = homedir();
|
|
12
|
+
return env;
|
|
13
|
+
}
|
|
6
14
|
export function createTools(deps) {
|
|
7
15
|
const wikiRead = defineTool("wiki_read", {
|
|
8
16
|
description: "Read a page from IO's knowledge base wiki. Path is relative to the wiki root (e.g., 'pages/preferences/editor.md').",
|
|
@@ -129,6 +137,7 @@ export function createTools(deps) {
|
|
|
129
137
|
timeout: (timeout_secs ?? 60) * 1000,
|
|
130
138
|
maxBuffer: 1024 * 1024,
|
|
131
139
|
cwd: working_dir,
|
|
140
|
+
env: shellEnv(),
|
|
132
141
|
});
|
|
133
142
|
const output = result.trim();
|
|
134
143
|
if (output.length > 8000) {
|
|
@@ -219,6 +228,7 @@ export function createTools(deps) {
|
|
|
219
228
|
encoding: "utf-8",
|
|
220
229
|
timeout: 60_000,
|
|
221
230
|
maxBuffer: 1024 * 1024,
|
|
231
|
+
env: shellEnv(),
|
|
222
232
|
});
|
|
223
233
|
const output = result.trim();
|
|
224
234
|
if (output.length > 8000) {
|
|
@@ -331,6 +341,7 @@ export function createTools(deps) {
|
|
|
331
341
|
encoding: "utf-8",
|
|
332
342
|
timeout: 30_000,
|
|
333
343
|
maxBuffer: 1024 * 1024,
|
|
344
|
+
env: shellEnv(),
|
|
334
345
|
});
|
|
335
346
|
const output = result.trim();
|
|
336
347
|
if (output.length > 8000) {
|
|
@@ -409,7 +420,133 @@ export function createTools(deps) {
|
|
|
409
420
|
}
|
|
410
421
|
},
|
|
411
422
|
});
|
|
412
|
-
|
|
423
|
+
// GitHub issue/PR management via gh CLI
|
|
424
|
+
const github = defineTool("github", {
|
|
425
|
+
description: "Manage GitHub issues and pull requests using the gh CLI. Supports creating, listing, viewing, and commenting on issues and PRs.",
|
|
426
|
+
skipPermission: true,
|
|
427
|
+
parameters: z.object({
|
|
428
|
+
action: z
|
|
429
|
+
.enum([
|
|
430
|
+
"create_issue",
|
|
431
|
+
"list_issues",
|
|
432
|
+
"view_issue",
|
|
433
|
+
"comment_issue",
|
|
434
|
+
"close_issue",
|
|
435
|
+
"create_pr",
|
|
436
|
+
"list_prs",
|
|
437
|
+
"view_pr",
|
|
438
|
+
"comment_pr",
|
|
439
|
+
])
|
|
440
|
+
.describe("The GitHub action to perform"),
|
|
441
|
+
repo: z.string().describe("Repository in owner/repo format"),
|
|
442
|
+
title: z.string().optional().describe("Title (for create_issue, create_pr)"),
|
|
443
|
+
body: z.string().optional().describe("Body text (for create_issue, create_pr, comment_*)"),
|
|
444
|
+
labels: z.array(z.string()).optional().describe("Labels (for create_issue)"),
|
|
445
|
+
assignees: z.array(z.string()).optional().describe("Assignees (for create_issue)"),
|
|
446
|
+
number: z.number().optional().describe("Issue or PR number (for view, comment, close)"),
|
|
447
|
+
base: z.string().optional().describe("Base branch (for create_pr)"),
|
|
448
|
+
head: z.string().optional().describe("Head branch (for create_pr)"),
|
|
449
|
+
state: z.enum(["open", "closed", "all"]).optional().describe("Filter by state (for list_*)"),
|
|
450
|
+
limit: z.number().optional().describe("Max results (for list_*, default 10)"),
|
|
451
|
+
}),
|
|
452
|
+
handler: async ({ action, repo, title, body, labels, assignees, number, base, head, state, limit }) => {
|
|
453
|
+
console.error(`[io] github tool called: ${action} on ${repo}`);
|
|
454
|
+
try {
|
|
455
|
+
let cmd;
|
|
456
|
+
const r = `--repo ${repo}`;
|
|
457
|
+
switch (action) {
|
|
458
|
+
case "create_issue": {
|
|
459
|
+
if (!title)
|
|
460
|
+
return "Error: title is required for create_issue";
|
|
461
|
+
cmd = `gh issue create ${r} --title "${title.replace(/"/g, '\\"')}"`;
|
|
462
|
+
if (body)
|
|
463
|
+
cmd += ` --body "${body.replace(/"/g, '\\"')}"`;
|
|
464
|
+
if (labels?.length)
|
|
465
|
+
cmd += ` --label "${labels.join(",")}"`;
|
|
466
|
+
if (assignees?.length)
|
|
467
|
+
cmd += ` --assignee "${assignees.join(",")}"`;
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case "list_issues": {
|
|
471
|
+
cmd = `gh issue list ${r} --limit ${limit ?? 10}`;
|
|
472
|
+
if (state)
|
|
473
|
+
cmd += ` --state ${state}`;
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case "view_issue": {
|
|
477
|
+
if (!number)
|
|
478
|
+
return "Error: number is required for view_issue";
|
|
479
|
+
cmd = `gh issue view ${number} ${r}`;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case "comment_issue": {
|
|
483
|
+
if (!number)
|
|
484
|
+
return "Error: number is required for comment_issue";
|
|
485
|
+
if (!body)
|
|
486
|
+
return "Error: body is required for comment_issue";
|
|
487
|
+
cmd = `gh issue comment ${number} ${r} --body "${body.replace(/"/g, '\\"')}"`;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case "close_issue": {
|
|
491
|
+
if (!number)
|
|
492
|
+
return "Error: number is required for close_issue";
|
|
493
|
+
cmd = `gh issue close ${number} ${r}`;
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
case "create_pr": {
|
|
497
|
+
if (!title)
|
|
498
|
+
return "Error: title is required for create_pr";
|
|
499
|
+
cmd = `gh pr create ${r} --title "${title.replace(/"/g, '\\"')}"`;
|
|
500
|
+
if (body)
|
|
501
|
+
cmd += ` --body "${body.replace(/"/g, '\\"')}"`;
|
|
502
|
+
if (base)
|
|
503
|
+
cmd += ` --base ${base}`;
|
|
504
|
+
if (head)
|
|
505
|
+
cmd += ` --head ${head}`;
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case "list_prs": {
|
|
509
|
+
cmd = `gh pr list ${r} --limit ${limit ?? 10}`;
|
|
510
|
+
if (state)
|
|
511
|
+
cmd += ` --state ${state}`;
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
case "view_pr": {
|
|
515
|
+
if (!number)
|
|
516
|
+
return "Error: number is required for view_pr";
|
|
517
|
+
cmd = `gh pr view ${number} ${r}`;
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
case "comment_pr": {
|
|
521
|
+
if (!number)
|
|
522
|
+
return "Error: number is required for comment_pr";
|
|
523
|
+
if (!body)
|
|
524
|
+
return "Error: body is required for comment_pr";
|
|
525
|
+
cmd = `gh pr comment ${number} ${r} --body "${body.replace(/"/g, '\\"')}"`;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
default:
|
|
529
|
+
return `Unknown action: ${action}`;
|
|
530
|
+
}
|
|
531
|
+
const result = execSync(cmd, {
|
|
532
|
+
encoding: "utf-8",
|
|
533
|
+
timeout: 30_000,
|
|
534
|
+
maxBuffer: 1024 * 1024,
|
|
535
|
+
env: shellEnv(),
|
|
536
|
+
}).trim();
|
|
537
|
+
if (result.length > 8000) {
|
|
538
|
+
return result.slice(0, 8000) + "\n\n[…truncated]";
|
|
539
|
+
}
|
|
540
|
+
return result || "(success, no output)";
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
const execErr = err;
|
|
544
|
+
const msg = execErr.stderr?.trim() || execErr.stdout?.trim() || execErr.message || "Command failed";
|
|
545
|
+
return `Error: ${msg.length > 4000 ? msg.slice(0, 4000) + "\n[…truncated]" : msg}`;
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
return [wikiRead, wikiWrite, wikiSearch, squadCreate, squadRecall, squadStatus, squadLogDecision, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
|
|
413
550
|
}
|
|
414
551
|
function walkDirectory(dir, maxDepth = 3, depth = 0) {
|
|
415
552
|
if (depth >= maxDepth)
|