fellow-agents 0.0.19 → 0.0.22
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 +14 -0
- package/dist/cli.js +36 -14
- package/dist/commands/clean.js +1 -0
- package/dist/commands/config.js +109 -0
- package/dist/commands/start.js +123 -2
- package/dist/lib/preferences.js +97 -0
- package/package.json +3 -2
- package/skills/tracker/SKILL.md +201 -0
package/README.md
CHANGED
|
@@ -12,6 +12,20 @@ fellow-agents
|
|
|
12
12
|
|
|
13
13
|
That's it. Creates a workspace, downloads what it needs, opens your browser — three agents ready to collaborate.
|
|
14
14
|
|
|
15
|
+
## What you get
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
A team of AI agents working together in one browser. Each session is an autonomous agent — they message each other, coordinate on tasks, and track shared work. All running locally, all visible at once.
|
|
20
|
+
|
|
21
|
+
- **(1) CLI sessions are agents.** Each one has its own identity, system prompt, and workspace.
|
|
22
|
+
- **(2) Bring your own AI.** Claude Code, GitHub Copilot CLI, pi, or any compatible CLI — mix and match per agent.
|
|
23
|
+
- **(3) Agents talk to each other.** Built-in async messaging (emcom) lets agents hand off tasks, request reviews, escalate questions — no shared context window, no token limits.
|
|
24
|
+
- **(4) Run one or many per tab.** Group agents by team or project. Switch between tabs like browser tabs.
|
|
25
|
+
- **(5) Built-in work tracker.** Every agent sees the same queue. File items, assign work, track status across sessions.
|
|
26
|
+
|
|
27
|
+
If you've ever wished Claude could send a sub-task to another instance and get an answer back while you keep working — this is that.
|
|
28
|
+
|
|
15
29
|
---
|
|
16
30
|
|
|
17
31
|
## What happens when you run it
|
package/dist/cli.js
CHANGED
|
@@ -4,8 +4,9 @@ const args = process.argv.slice(2);
|
|
|
4
4
|
const command = args[0] === "stop" ? "stop"
|
|
5
5
|
: args[0] === "clean" ? "clean"
|
|
6
6
|
: args[0] === "uninstall" ? "uninstall"
|
|
7
|
-
:
|
|
8
|
-
: "
|
|
7
|
+
: args[0] === "config" ? "config"
|
|
8
|
+
: (args[0] === "--help" || args[0] === "-h") ? "help"
|
|
9
|
+
: "start";
|
|
9
10
|
function getFlag(name, fallback) {
|
|
10
11
|
const idx = args.indexOf(name);
|
|
11
12
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : fallback;
|
|
@@ -38,23 +39,44 @@ else if (command === "uninstall") {
|
|
|
38
39
|
yes: hasFlag("--yes"),
|
|
39
40
|
});
|
|
40
41
|
}
|
|
42
|
+
else if (command === "config") {
|
|
43
|
+
const { config } = await import("./commands/config.js");
|
|
44
|
+
config(args.slice(1));
|
|
45
|
+
}
|
|
41
46
|
else {
|
|
42
|
-
console.log(`fellow-agents — multi-agent system for Claude Code
|
|
47
|
+
console.log(`fellow-agents — multi-agent system for Claude Code, Copilot CLI, and pi
|
|
48
|
+
|
|
49
|
+
Multiple AI sessions on one machine, collaborating via email-style
|
|
50
|
+
messaging (emcom). Type fellow-agents to download binaries, scaffold
|
|
51
|
+
three sample agents (coder, coordinator, reviewer), and open the
|
|
52
|
+
browser UI. Each agent runs in its own pty-win pane.
|
|
43
53
|
|
|
44
|
-
|
|
45
|
-
fellow-agents [options] Start services (
|
|
46
|
-
fellow-agents stop Stop
|
|
47
|
-
fellow-agents clean Wipe cached binaries + pty-win
|
|
48
|
-
fellow-agents uninstall [--yes] Remove all
|
|
54
|
+
Commands:
|
|
55
|
+
fellow-agents [options] Start services (the usual command)
|
|
56
|
+
fellow-agents stop Stop running services
|
|
57
|
+
fellow-agents clean Wipe cached binaries + pty-win install (preserves logs and preferences)
|
|
58
|
+
fellow-agents uninstall [--yes] Remove all state, including scaffolded workspaces
|
|
59
|
+
fellow-agents config <get|set> Read or write user preferences (see 'config --help')
|
|
49
60
|
|
|
50
|
-
|
|
61
|
+
Start options:
|
|
51
62
|
--port <number> pty-win port (default: 3700)
|
|
52
63
|
--emcom-port <number> emcom-server port (default: 8800)
|
|
53
|
-
--dir <path> Working directory (default: current)
|
|
54
|
-
--no-browser Don't open browser
|
|
55
|
-
--update Force re-download binaries
|
|
56
|
-
|
|
64
|
+
--dir <path> Working directory for agent workspaces (default: current)
|
|
65
|
+
--no-browser Don't open browser after services start
|
|
66
|
+
--update Force re-download platform binaries
|
|
67
|
+
|
|
68
|
+
Uninstall options:
|
|
69
|
+
--yes Actually perform the uninstall (default is dry-run preview)
|
|
70
|
+
--dir <path> Workspace location (default: current — use if you ran start elsewhere)
|
|
71
|
+
|
|
72
|
+
Config:
|
|
73
|
+
fellow-agents config get [key] Print all preferences, or one value
|
|
74
|
+
fellow-agents config set <key> <value> Write a preference (e.g. cliPreference claude)
|
|
75
|
+
|
|
76
|
+
General:
|
|
77
|
+
-h, --help Show this help
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
First time? Just run \`fellow-agents\` and follow the prompts.
|
|
80
|
+
Docs: https://github.com/rajan-chari/fellow-agents`);
|
|
59
81
|
}
|
|
60
82
|
export {};
|
package/dist/commands/clean.js
CHANGED
|
@@ -67,6 +67,7 @@ export function clean() {
|
|
|
67
67
|
console.log("");
|
|
68
68
|
console.log(` Cleaned ${formatBytes(totalFreed)} from ${dataDir}`);
|
|
69
69
|
console.log(` Logs preserved at ${join(dataDir, "logs")}`);
|
|
70
|
+
console.log(` Preferences preserved at ${join(dataDir, "preferences.json")} (if set)`);
|
|
70
71
|
console.log(` Run 'fellow-agents' to reinstall.`);
|
|
71
72
|
console.log("");
|
|
72
73
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readPreferences, writePreferences, lookupCli, preferencesFile, KNOWN_KEYS, } from "../lib/preferences.js";
|
|
2
|
+
function printHelp() {
|
|
3
|
+
console.log(`fellow-agents config — read or write user preferences
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
fellow-agents config get Print all preferences as JSON
|
|
7
|
+
fellow-agents config get <key> Print the value of a single key
|
|
8
|
+
fellow-agents config set <key> <value> Write a value (creates the file if missing)
|
|
9
|
+
|
|
10
|
+
Known keys:
|
|
11
|
+
cliPreference The CLI launched by pty-win's play button.
|
|
12
|
+
Bare command (claude | copilot | pi) or full path to an executable.
|
|
13
|
+
|
|
14
|
+
File: ${preferencesFile}
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
fellow-agents config set cliPreference claude
|
|
18
|
+
fellow-agents config set cliPreference "C:\\Program Files\\claude\\claude.exe"
|
|
19
|
+
fellow-agents config get cliPreference`);
|
|
20
|
+
}
|
|
21
|
+
function isKnownKey(key) {
|
|
22
|
+
return KNOWN_KEYS.includes(key);
|
|
23
|
+
}
|
|
24
|
+
export function config(args) {
|
|
25
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
26
|
+
printHelp();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const sub = args[0];
|
|
30
|
+
if (sub === "get") {
|
|
31
|
+
handleGet(args.slice(1));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (sub === "set") {
|
|
35
|
+
handleSet(args.slice(1));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.error(`Unknown config subcommand: ${sub}`);
|
|
39
|
+
console.error(`Run 'fellow-agents config --help' for usage.`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
function handleGet(args) {
|
|
43
|
+
const prefs = readPreferences();
|
|
44
|
+
if (args.length === 0) {
|
|
45
|
+
if (prefs === null) {
|
|
46
|
+
console.log("No preferences set.");
|
|
47
|
+
console.log(`Run 'fellow-agents' or 'fellow-agents config set <key> <value>' to create them.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log(JSON.stringify(prefs, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const key = args[0];
|
|
54
|
+
if (!isKnownKey(key)) {
|
|
55
|
+
console.error(`Unknown key: ${key}`);
|
|
56
|
+
console.error(`Known keys: ${KNOWN_KEYS.join(", ")}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
if (prefs === null) {
|
|
60
|
+
console.log("No preferences set.");
|
|
61
|
+
console.log(`Run 'fellow-agents' or 'fellow-agents config set ${key} <value>' to set it.`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const value = prefs[key];
|
|
65
|
+
if (value === undefined || value === null || value === "") {
|
|
66
|
+
console.log(`${key} is not set.`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
console.log(String(value));
|
|
70
|
+
}
|
|
71
|
+
function handleSet(args) {
|
|
72
|
+
if (args.length < 2) {
|
|
73
|
+
console.error("Usage: fellow-agents config set <key> <value>");
|
|
74
|
+
console.error(`Known keys: ${KNOWN_KEYS.join(", ")}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const key = args[0];
|
|
78
|
+
const value = args.slice(1).join(" ");
|
|
79
|
+
if (!isKnownKey(key)) {
|
|
80
|
+
console.error(`Unknown key: ${key}`);
|
|
81
|
+
console.error(`Known keys: ${KNOWN_KEYS.join(", ")}`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (!value.trim()) {
|
|
85
|
+
console.error(`Value for '${key}' cannot be empty.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
// cliPreference: warn (don't fail) if where.exe / which doesn't find it.
|
|
89
|
+
// Real use case: user pre-configures before installing the CLI, or the CLI
|
|
90
|
+
// is in a non-default location PATH doesn't see yet. Reject would block valid flows.
|
|
91
|
+
if (key === "cliPreference") {
|
|
92
|
+
const looksLikePath = value.includes("\\") || value.includes("/");
|
|
93
|
+
const resolved = lookupCli(value);
|
|
94
|
+
if (resolved === null && !looksLikePath) {
|
|
95
|
+
const tool = process.platform === "win32" ? "where.exe" : "which";
|
|
96
|
+
console.error(` WARNING: '${tool} ${value}' returned no matches.`);
|
|
97
|
+
console.error(` Writing the preference anyway — pty-win will fall back if the CLI is missing at launch time.`);
|
|
98
|
+
console.error(` Fix later with: fellow-agents config set cliPreference <name-or-path>`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const existing = readPreferences();
|
|
102
|
+
const written = writePreferences({
|
|
103
|
+
...(existing ?? {}),
|
|
104
|
+
[key]: value,
|
|
105
|
+
updatedBy: "config-set",
|
|
106
|
+
});
|
|
107
|
+
console.log(`Set ${key} = ${value}`);
|
|
108
|
+
console.log(`Wrote ${preferencesFile} (schema ${written.schema}, updatedAt ${written.updatedAt}).`);
|
|
109
|
+
}
|
package/dist/commands/start.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
2
2
|
import http from "http";
|
|
3
3
|
import { join, resolve } from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
|
-
import
|
|
5
|
+
import * as readline from "node:readline/promises";
|
|
6
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
7
|
+
import { binDir, ptyWinDir, logsDir, dataDir } from "../lib/paths.js";
|
|
6
8
|
import { downloadBinaries } from "../lib/download.js";
|
|
7
9
|
import { startEmcomServer, startPtyWin, stopAll, logPath } from "../lib/services.js";
|
|
8
10
|
import { scaffoldWorkspaces, registerAgents, writeHooks } from "../lib/workspaces.js";
|
|
9
11
|
import { installSkills } from "../lib/skills.js";
|
|
10
12
|
import { binarySuffix } from "../lib/platform.js";
|
|
13
|
+
import { readPreferences, writePreferences, autoDetectClis, lookupCli, } from "../lib/preferences.js";
|
|
11
14
|
// Minimal engines.node range check — handles ">=N", "<N", and combinations.
|
|
12
15
|
function nodeInRange(version, range) {
|
|
13
16
|
const major = parseInt(version.split(".")[0], 10);
|
|
@@ -17,11 +20,129 @@ function nodeInRange(version, range) {
|
|
|
17
20
|
const max = maxMatch ? parseInt(maxMatch[1], 10) : Infinity;
|
|
18
21
|
return major >= min && major < max;
|
|
19
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* First-run CLI preference prompt. Returns the chosen value (bare command or full path),
|
|
25
|
+
* or null if we should skip (non-interactive, or user declined).
|
|
26
|
+
*
|
|
27
|
+
* Design (locked 5/27 with milo):
|
|
28
|
+
* - Native readline (no inquirer dep — start.ts is a setup pipeline, not a chat UI)
|
|
29
|
+
* - Auto-detected CLIs from PATH shown as numbered choices, plus a "Custom path" option
|
|
30
|
+
* - If user types a custom value not on PATH, single confirm: "Use it anyway? [y/N]"
|
|
31
|
+
* - Non-interactive stdin (CI, piped) → return null, caller logs a hint
|
|
32
|
+
*/
|
|
33
|
+
async function promptForCliPreference() {
|
|
34
|
+
if (!input.isTTY)
|
|
35
|
+
return null;
|
|
36
|
+
const detected = autoDetectClis();
|
|
37
|
+
const rl = readline.createInterface({ input, output });
|
|
38
|
+
try {
|
|
39
|
+
console.log("");
|
|
40
|
+
console.log(" Pick your preferred CLI — pty-win's play button will launch this in each new tab.");
|
|
41
|
+
console.log(" (You can change it later with 'fellow-agents config set cliPreference <name>'.)");
|
|
42
|
+
console.log("");
|
|
43
|
+
const choices = [...detected];
|
|
44
|
+
if (detected.length > 0) {
|
|
45
|
+
for (let i = 0; i < detected.length; i++) {
|
|
46
|
+
console.log(` [${i + 1}] ${detected[i]}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(" (None of claude/copilot/pi found on PATH — pick Custom path to specify your own)");
|
|
51
|
+
}
|
|
52
|
+
const customIdx = choices.length + 1;
|
|
53
|
+
console.log(` [${customIdx}] Custom path or other command`);
|
|
54
|
+
console.log(` [s] Skip for now`);
|
|
55
|
+
console.log("");
|
|
56
|
+
const answer = (await rl.question(" Choice: ")).trim().toLowerCase();
|
|
57
|
+
if (answer === "s" || answer === "skip" || answer === "") {
|
|
58
|
+
console.log(" Skipped — pty-win will pick a default until you set one.");
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const num = parseInt(answer, 10);
|
|
62
|
+
if (!isNaN(num) && num >= 1 && num <= detected.length) {
|
|
63
|
+
return detected[num - 1];
|
|
64
|
+
}
|
|
65
|
+
if (!isNaN(num) && num === customIdx) {
|
|
66
|
+
const value = (await rl.question(" Enter command or full path: ")).trim();
|
|
67
|
+
if (!value) {
|
|
68
|
+
console.log(" Empty input — skipped.");
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
// Confirm step if value doesn't resolve and doesn't look like a path
|
|
72
|
+
const looksLikePath = value.includes("\\") || value.includes("/");
|
|
73
|
+
const resolved = lookupCli(value);
|
|
74
|
+
if (resolved === null && !looksLikePath) {
|
|
75
|
+
const tool = process.platform === "win32" ? "where.exe" : "which";
|
|
76
|
+
console.log(` '${tool} ${value}' returned no matches.`);
|
|
77
|
+
const confirm = (await rl.question(" Use it anyway? [y/N]: ")).trim().toLowerCase();
|
|
78
|
+
if (confirm !== "y" && confirm !== "yes") {
|
|
79
|
+
console.log(" Skipped.");
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
console.log(` Unrecognised choice '${answer}' — skipped.`);
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
rl.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
20
92
|
export async function start(opts) {
|
|
21
93
|
console.log("");
|
|
22
94
|
console.log(" fellow-agents");
|
|
23
95
|
console.log(" =============");
|
|
24
96
|
console.log("");
|
|
97
|
+
// First-run welcome — show a brief orientation the very first time someone
|
|
98
|
+
// types `fellow-agents`. Marker lives in dataDir so it survives across
|
|
99
|
+
// sessions but resets if the user runs `fellow-agents uninstall`.
|
|
100
|
+
const firstRunMarker = join(dataDir, ".first-run");
|
|
101
|
+
if (!existsSync(firstRunMarker)) {
|
|
102
|
+
console.log(" Welcome! This is your first run. Here's what's about to happen:");
|
|
103
|
+
console.log("");
|
|
104
|
+
console.log(" [1] Download platform binaries (emcom, tracker, emcom-server)");
|
|
105
|
+
console.log(" [2] Install pty-win (the browser-based terminal multiplexer)");
|
|
106
|
+
console.log(" [3] Scaffold three agent workspaces (coder, coordinator, reviewer)");
|
|
107
|
+
console.log(" [4] Install agent skills into ~/.claude/, ~/.copilot/, ~/.agents/");
|
|
108
|
+
console.log(" [5] Start emcom-server and pty-win, open the browser UI");
|
|
109
|
+
console.log("");
|
|
110
|
+
console.log(" Press Ctrl+C any time to stop. `fellow-agents --help` shows options.");
|
|
111
|
+
console.log("");
|
|
112
|
+
try {
|
|
113
|
+
mkdirSync(dataDir, { recursive: true });
|
|
114
|
+
writeFileSync(firstRunMarker, new Date().toISOString());
|
|
115
|
+
}
|
|
116
|
+
catch { }
|
|
117
|
+
}
|
|
118
|
+
// CLI preference prompt — fires when preferences.json is missing or has no cliPreference.
|
|
119
|
+
// Independent of the first-run marker so a user who wipes preferences.json (or upgrades from
|
|
120
|
+
// a pre-0.0.22 install) gets prompted on the next run. Non-interactive sessions skip silently.
|
|
121
|
+
const existingPrefs = readPreferences();
|
|
122
|
+
if (existingPrefs === null || !existingPrefs.cliPreference) {
|
|
123
|
+
const chosen = await promptForCliPreference();
|
|
124
|
+
if (chosen) {
|
|
125
|
+
try {
|
|
126
|
+
writePreferences({
|
|
127
|
+
...(existingPrefs ?? {}),
|
|
128
|
+
cliPreference: chosen,
|
|
129
|
+
updatedBy: "first-run-prompt",
|
|
130
|
+
});
|
|
131
|
+
console.log(` CLI preference set: ${chosen}`);
|
|
132
|
+
console.log("");
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
console.error(` Failed to write preferences: ${err.message}`);
|
|
136
|
+
console.error(` Continuing without a stored preference.`);
|
|
137
|
+
console.log("");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (!input.isTTY) {
|
|
141
|
+
console.log(" (non-interactive — skipping CLI preference setup)");
|
|
142
|
+
console.log(` Set later with: fellow-agents config set cliPreference <name-or-path>`);
|
|
143
|
+
console.log("");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
25
146
|
// Auto-create fellow-agents/ subdirectory if CWD doesn't already have workspaces/
|
|
26
147
|
let workDir = resolve(opts.dir);
|
|
27
148
|
if (!existsSync(join(workDir, "workspaces"))) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync, unlinkSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { dataDir } from "./paths.js";
|
|
5
|
+
/** ~/.fellow-agents/preferences.json */
|
|
6
|
+
export const preferencesFile = join(dataDir, "preferences.json");
|
|
7
|
+
export const CURRENT_SCHEMA = 1;
|
|
8
|
+
/** Known CLI names — used by autoDetectClis() and as the default preset list for first-run prompt. */
|
|
9
|
+
export const KNOWN_CLIS = ["claude", "copilot", "pi"];
|
|
10
|
+
/** Known preference keys (for config get/set validation + --help listing). */
|
|
11
|
+
export const KNOWN_KEYS = ["cliPreference"];
|
|
12
|
+
export const KEY_SCHEMAS = {
|
|
13
|
+
cliPreference: {
|
|
14
|
+
type: "select",
|
|
15
|
+
label: "Default CLI",
|
|
16
|
+
description: "The CLI launched by pty-win's play button in each new tab.",
|
|
17
|
+
options: [...KNOWN_CLIS],
|
|
18
|
+
allowCustom: true,
|
|
19
|
+
customLabel: "Custom path…",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Read preferences from disk.
|
|
24
|
+
* Returns null if file is missing.
|
|
25
|
+
* Returns { schema: CURRENT_SCHEMA } with a warning on the console if file exists but is malformed —
|
|
26
|
+
* caller can decide whether to re-prompt.
|
|
27
|
+
*/
|
|
28
|
+
export function readPreferences() {
|
|
29
|
+
if (!existsSync(preferencesFile))
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const raw = readFileSync(preferencesFile, "utf-8");
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
35
|
+
throw new Error("not an object");
|
|
36
|
+
if (typeof parsed.schema !== "number")
|
|
37
|
+
throw new Error("missing schema");
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(` WARNING: ${preferencesFile} is malformed (${err.message}). Treating as unset.`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Write preferences atomically (temp + renameSync — atomic on Windows from Node 10+).
|
|
47
|
+
* Always stamps `updatedAt` to now and `updatedBy` to the supplied value.
|
|
48
|
+
* Ensures dataDir exists. Cleans up the temp file on failure.
|
|
49
|
+
*/
|
|
50
|
+
export function writePreferences(prefs) {
|
|
51
|
+
mkdirSync(dataDir, { recursive: true });
|
|
52
|
+
const next = {
|
|
53
|
+
schema: prefs.schema ?? CURRENT_SCHEMA,
|
|
54
|
+
cliPreference: prefs.cliPreference,
|
|
55
|
+
updatedAt: new Date().toISOString(),
|
|
56
|
+
updatedBy: prefs.updatedBy,
|
|
57
|
+
};
|
|
58
|
+
const tmp = `${preferencesFile}.tmp`;
|
|
59
|
+
try {
|
|
60
|
+
writeFileSync(tmp, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
61
|
+
renameSync(tmp, preferencesFile);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
try {
|
|
65
|
+
if (existsSync(tmp))
|
|
66
|
+
unlinkSync(tmp);
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
return next;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Look up a CLI on PATH. Returns the resolved full path (first hit), or null if not found.
|
|
75
|
+
* Uses `where.exe` on Windows and `which -a` on Unix. Never throws.
|
|
76
|
+
*/
|
|
77
|
+
export function lookupCli(name) {
|
|
78
|
+
const cmd = process.platform === "win32" ? `where.exe ${name}` : `which ${name}`;
|
|
79
|
+
try {
|
|
80
|
+
const out = execSync(cmd, { stdio: ["ignore", "pipe", "ignore"] })
|
|
81
|
+
.toString()
|
|
82
|
+
.split(/\r?\n/)
|
|
83
|
+
.map((s) => s.trim())
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
return out.length > 0 ? out[0] : null;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Auto-detect which of the KNOWN_CLIS are on PATH.
|
|
93
|
+
* Returns the subset that resolved, preserving the KNOWN_CLIS priority order.
|
|
94
|
+
*/
|
|
95
|
+
export function autoDetectClis() {
|
|
96
|
+
return KNOWN_CLIS.filter((name) => lookupCli(name) !== null);
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fellow-agents",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.22",
|
|
4
4
|
"description": "Multi-agent system — multiple Claude Code instances collaborating via messaging",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
21
|
-
"prepublishOnly": "npm run build"
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"postinstall": "node -e \"process.env.npm_config_global && console.log('\\nfellow-agents installed. Run \\\"fellow-agents\\\" to start, or \\\"fellow-agents --help\\\" for options.\\n')\""
|
|
22
23
|
},
|
|
23
24
|
"engines": {
|
|
24
25
|
"node": ">=18"
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tracker
|
|
3
|
+
description: |
|
|
4
|
+
Use this skill when the user wants to interact with the team task tracker,
|
|
5
|
+
or when the agent itself needs to view its own work queue or file/update
|
|
6
|
+
tracker items.
|
|
7
|
+
Triggers: check queue, my work, what's next, file a tracker item,
|
|
8
|
+
open issue, update status, mark merged, close item, decisions pending,
|
|
9
|
+
blocked items, stale items, list bugs, list PRs, work item history,
|
|
10
|
+
link items, or arrival of an emcom auto-inject prompt that references
|
|
11
|
+
tracker work.
|
|
12
|
+
tools:
|
|
13
|
+
- Bash
|
|
14
|
+
- Read
|
|
15
|
+
- Write
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# tracker Skill
|
|
19
|
+
|
|
20
|
+
The team **tracker** is a cross-session, cross-agent record of in-flight work: bugs, PRs, investigations, and decisions. It is the source of truth for "what work is open right now" — distinct from briefing.md (per-session narrative) and field-notes.md (persistent gotchas).
|
|
21
|
+
|
|
22
|
+
A tracker item exists when work spans sessions or involves more than one agent. A briefing entry covers what your current self did. A field-note captures a one-shot gotcha you want to remember forever. If you can't answer "is this still in flight" or "who's working on this" by reading just your briefing, the work belongs in tracker.
|
|
23
|
+
|
|
24
|
+
## Mental model
|
|
25
|
+
|
|
26
|
+
- **Items** have status, severity, labels, and assignment.
|
|
27
|
+
- **Queue** is your slice — `tracker queue` shows items assigned to you, or referenced in ways you should act on.
|
|
28
|
+
- **Statuses** drive workflow — items move through a defined lifecycle (see below). The status field is **strictly validated**: generic terms (`in_progress`, `done`, `completed`) are rejected. Use the canonical set.
|
|
29
|
+
- **History** is append-only — every status change, decision, and comment is preserved.
|
|
30
|
+
|
|
31
|
+
## When to file a tracker item
|
|
32
|
+
|
|
33
|
+
File a tracker item when ANY of:
|
|
34
|
+
- The work is likely to span more than one session.
|
|
35
|
+
- More than one agent will touch it.
|
|
36
|
+
- It needs an owner and a status that other agents will check.
|
|
37
|
+
- It's a decision that future-you should be able to find via `tracker decisions`.
|
|
38
|
+
|
|
39
|
+
Don't file when:
|
|
40
|
+
- You're capturing a personal note → use briefing.md or field-notes.md.
|
|
41
|
+
- The work completes in this session → just do it and log in briefing.
|
|
42
|
+
- It's a coding gotcha you want to remember → field-notes.md.
|
|
43
|
+
|
|
44
|
+
When in doubt, file. Tracker entries are cheap; missed coordination is expensive.
|
|
45
|
+
|
|
46
|
+
## Daily flow
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
tracker queue # what's assigned to me
|
|
50
|
+
tracker view <id> # full details + history
|
|
51
|
+
tracker update <id> --status <next> # advance the workflow
|
|
52
|
+
tracker comment <id> "<note>" # add context without status change
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Reference forms (all accepted):
|
|
56
|
+
- UUID prefix (first 8 hex chars work for `view`/`update`)
|
|
57
|
+
- `repo#number` (e.g. `banana#42`)
|
|
58
|
+
- Bare number when there's no ambiguity
|
|
59
|
+
|
|
60
|
+
## Status lifecycle
|
|
61
|
+
|
|
62
|
+
The canonical 11 statuses, in typical workflow order:
|
|
63
|
+
|
|
64
|
+
| Status | Meaning |
|
|
65
|
+
|--------|---------|
|
|
66
|
+
| `new` | Just filed; no triage yet |
|
|
67
|
+
| `triaged` | Reviewed, has owner + severity, waiting to start |
|
|
68
|
+
| `investigating` | Actively working on understanding the problem |
|
|
69
|
+
| `findings-reported` | Investigation done, findings documented in --notes |
|
|
70
|
+
| `decision-pending` | Needs a call from Rajan or another agent before proceeding |
|
|
71
|
+
| `pr-up` | Code change opened as a PR, awaiting review |
|
|
72
|
+
| `testing` | PR merged or staged, validating in real env |
|
|
73
|
+
| `ready-to-merge` | Reviewed + approved, awaiting merge |
|
|
74
|
+
| `merged` | Code is in the target branch |
|
|
75
|
+
| `deferred` | Real item, intentionally postponed (`--blocker` should explain) |
|
|
76
|
+
| `closed` | Resolved, no further action |
|
|
77
|
+
|
|
78
|
+
**Critical gotcha**: generic terms like `in_progress`, `done`, `completed`, `wip`, `fixed` are **rejected** by `tracker update --status`. If you mean "actively working on it" use `investigating`. If you mean "code is in" use `merged`. If you mean "fully done" use `closed`.
|
|
79
|
+
|
|
80
|
+
`tracker list --status open` shows everything non-closed (anything except `merged`, `deferred`, `closed`).
|
|
81
|
+
|
|
82
|
+
## Filing a new item
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
tracker create --repo banana \
|
|
86
|
+
--title "verify-window too tight for Copilot hooks" \
|
|
87
|
+
--type investigation \
|
|
88
|
+
--severity normal \
|
|
89
|
+
--assigned milo \
|
|
90
|
+
--opened-by Rajan \
|
|
91
|
+
--notes "Copilot's PowerShell hook stack takes ~200ms × 3 steps. Current 5s verify often expires."
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Required: `--repo`, `--title`. Type defaults to `issue`; alternatives are `pr`, `investigation`, `decision`. Severity defaults to `normal`; alternatives `low`, `high`, `critical`. Status defaults to `new`.
|
|
95
|
+
|
|
96
|
+
`--opened-by` is the person/agent who originally reported the issue (distinct from `--assigned`, which is who works it). Useful when an emcom thread or external party flagged something and you're filing on their behalf.
|
|
97
|
+
|
|
98
|
+
## Updating an item
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
tracker update banana#42 --status investigating --assigned milo --append-notes "Started looking at session.ts verify path."
|
|
102
|
+
tracker update <id> --findings "Root cause: ..." --status findings-reported
|
|
103
|
+
tracker update <id> --decision "Bump VERIFY_WINDOW_MS to 8s for Copilot only" --decision-rationale "..." --status decision-pending
|
|
104
|
+
tracker update <id> --status pr-up --pr 142
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Notes vs append-notes:
|
|
108
|
+
- `--notes <text>` **replaces** the entire notes field.
|
|
109
|
+
- `--append-notes <text>` **appends** a timestamped entry. Prefer this.
|
|
110
|
+
|
|
111
|
+
If something is blocking progress: `--status deferred --blocker "Waiting on upstream node-pty Node 26 prebuilts"`.
|
|
112
|
+
|
|
113
|
+
## Comments vs notes
|
|
114
|
+
|
|
115
|
+
- `--append-notes` lives on the item, shown in `view`. Use for substantive findings, status reasoning, links to PRs.
|
|
116
|
+
- `tracker comment <id> "<text>"` adds a separate comment record (also shown in history). Use for lightweight chatter that doesn't change the item's state.
|
|
117
|
+
|
|
118
|
+
## Linking items
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
tracker link <id1> <id2> --type related # default
|
|
122
|
+
tracker link <id1> <id2> --type blocks # id1 blocks id2
|
|
123
|
+
tracker link <id1> <id2> --type blocked-by # id1 blocked by id2
|
|
124
|
+
tracker link <id1> <id2> --type duplicate # id1 duplicates id2
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use links instead of restating context across items. Especially `blocks` / `blocked-by` for chains where one decision unblocks several follow-ups.
|
|
128
|
+
|
|
129
|
+
## Useful queries
|
|
130
|
+
|
|
131
|
+
| Need | Command |
|
|
132
|
+
|------|---------|
|
|
133
|
+
| My open items | `tracker queue` |
|
|
134
|
+
| Another agent's queue | `tracker queue <agent-name>` |
|
|
135
|
+
| Everything not closed | `tracker list --status open` |
|
|
136
|
+
| Items needing decision | `tracker list --needs-decision` (alias for `--status decision-pending`) |
|
|
137
|
+
| Blocked items | `tracker blocked` |
|
|
138
|
+
| Stale items (>24h no update) | `tracker stale` or `tracker stale --hours 48` |
|
|
139
|
+
| Decisions made | `tracker decisions` |
|
|
140
|
+
| Find by text | `tracker search "<query>"` |
|
|
141
|
+
| Item history | `tracker history <id>` |
|
|
142
|
+
| Team activity | `tracker report --period 7d` |
|
|
143
|
+
| Per-person breakdown | `tracker report people` |
|
|
144
|
+
| SLA on open items | `tracker report sla` |
|
|
145
|
+
| GitHub-linked activity | `tracker github --period 7d` |
|
|
146
|
+
| Summary counts | `tracker stats` |
|
|
147
|
+
|
|
148
|
+
## Labels and severity
|
|
149
|
+
|
|
150
|
+
- Labels are freeform comma-separated tags. Use sparingly and consistently — `tracker list --label <name>` finds items by label. Conventional labels: `regression`, `flake`, `infra`, `docs`, `breaking-change`.
|
|
151
|
+
- Severity: `low` / `normal` / `high` / `critical`. Default normal. Use `critical` only for production-impacting or blocking-the-team issues.
|
|
152
|
+
|
|
153
|
+
## Permission-friendly invocation
|
|
154
|
+
|
|
155
|
+
(Matters in CLIs that gate command execution.)
|
|
156
|
+
|
|
157
|
+
- **One command per Bash call.** Do NOT chain with `&&`, `;`, or `||`.
|
|
158
|
+
- Do NOT assign the binary path to a variable. Use the bare command.
|
|
159
|
+
- **Independent** queries (e.g. `queue` + `blocked` + `stale`): parallel Bash calls.
|
|
160
|
+
- **Sequential** updates (e.g. create then assign): separate sequential calls.
|
|
161
|
+
|
|
162
|
+
## Error recovery
|
|
163
|
+
|
|
164
|
+
**Auth error** ("Missing X-Tracker-Name header"): identical pattern to emcom — register via the team's identity flow. In most workspaces, the `identity.json` registered for emcom also identifies you to tracker, so the fix is usually `cd` to a workspace where you have an identity, not re-registering.
|
|
165
|
+
|
|
166
|
+
**Connection error** ("Connection refused"): the tracker shares a server with emcom on port 8800. Start it:
|
|
167
|
+
```bash
|
|
168
|
+
emcom-server &
|
|
169
|
+
sleep 2
|
|
170
|
+
```
|
|
171
|
+
Then retry the tracker command.
|
|
172
|
+
|
|
173
|
+
**Validation error on status**: you used a generic term. Map to the canonical 11:
|
|
174
|
+
- `in_progress`, `working`, `wip` → `investigating`
|
|
175
|
+
- `done`, `fixed`, `complete` → either `merged` (code is in) or `closed` (resolved)
|
|
176
|
+
- `waiting`, `pending` → either `decision-pending` (waiting on decision) or `deferred` (intentionally postponed)
|
|
177
|
+
- `review` → `pr-up` (PR open) or `ready-to-merge` (approved, awaiting merge)
|
|
178
|
+
|
|
179
|
+
**Missing repo on create**: `--repo` is required. Use the short name from the user's workspace (e.g. `banana`, `fellow-agents`, `pty-cld`).
|
|
180
|
+
|
|
181
|
+
## Running commands
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
tracker <subcommand> [args]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`tracker` is on PATH when fellow-agents has been installed (`npm install -g fellow-agents`) — the npm shim wraps `~/.fellow-agents/bin/<platform>/tracker`. Use the bare command. Do not prepend any skills-directory path.
|
|
188
|
+
|
|
189
|
+
If a CLI environment can't find `tracker` on PATH, that means fellow-agents isn't installed (or its bin shim isn't picked up by the shell). Tell the user to run `npm install -g fellow-agents` rather than guessing at a skill-bundled path — fellow-agents does not ship binaries inside `~/.claude/skills/`, `~/.copilot/skills/`, or `~/.agents/skills/`.
|
|
190
|
+
|
|
191
|
+
## Output
|
|
192
|
+
|
|
193
|
+
Tracker output is structured — tables for lists, key/value blocks for `view`, chronological lists for `history`. Present output as-returned; don't reformat. For one item view, show the full structure. For lists, show as-is (already paginated/abbreviated by tracker itself).
|
|
194
|
+
|
|
195
|
+
## Notes
|
|
196
|
+
|
|
197
|
+
- Item IDs are UUIDs; first 8 hex chars work as prefix everywhere.
|
|
198
|
+
- Server: port 8800 (shared with emcom), data in `~/.emcom/` alongside emcom state.
|
|
199
|
+
- Always use `127.0.0.1` rather than `localhost` (avoids IPv6 DNS resolution penalty on Windows).
|
|
200
|
+
- `tracker history` shows every state change with timestamp and the agent that made it — useful for "who decided what when".
|
|
201
|
+
- Backtick-containing notes via `--notes "..."` get shell-expanded — single-quote when the body contains backticks or `$`.
|