zerg-status 0.1.0 → 1.0.0

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1 @@
1
+ export declare function init(): void;
@@ -0,0 +1,6 @@
1
+ import { createSession } from '../state.js';
2
+ import { formatSessionOutput } from '../output.js';
3
+ export function init() {
4
+ const sessionId = createSession();
5
+ console.log(formatSessionOutput(sessionId));
6
+ }
@@ -0,0 +1 @@
1
+ export declare function nowCommand(sessionId: string, text: string): void;
@@ -0,0 +1,8 @@
1
+ import { setNow } from '../state.js';
2
+ import { validateNow } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function nowCommand(sessionId, text) {
5
+ const validText = validateNow(text);
6
+ const state = setNow(sessionId, validText);
7
+ console.log(formatStatusOutput(state));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function stateCommand(sessionId: string, stateValue: string, question?: string): void;
@@ -0,0 +1,8 @@
1
+ import { setState } from '../state.js';
2
+ import { validateState } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function stateCommand(sessionId, stateValue, question) {
5
+ const validState = validateState(stateValue);
6
+ const state = setState(sessionId, validState, question);
7
+ console.log(formatStatusOutput(state));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function summaryCommand(sessionId: string, text: string): void;
@@ -0,0 +1,8 @@
1
+ import { setSummary } from '../state.js';
2
+ import { validateSummary } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function summaryCommand(sessionId, text) {
5
+ const validText = validateSummary(text);
6
+ const state = setSummary(sessionId, validText);
7
+ console.log(formatStatusOutput(state));
8
+ }
@@ -0,0 +1 @@
1
+ export declare function taskCommand(sessionId: string, action: string, taskDescription?: string): void;
@@ -0,0 +1,25 @@
1
+ import { addTask, updateTaskStatus, clearTasks } from '../state.js';
2
+ import { validateTaskStatus } from '../validate.js';
3
+ import { formatStatusOutput } from '../output.js';
4
+ export function taskCommand(sessionId, action, taskDescription) {
5
+ if (action === 'clear') {
6
+ const state = clearTasks(sessionId);
7
+ console.log(formatStatusOutput(state));
8
+ return;
9
+ }
10
+ if (action === 'add') {
11
+ if (!taskDescription) {
12
+ throw new Error('Task description required for "add" action');
13
+ }
14
+ const state = addTask(sessionId, taskDescription, 'todo');
15
+ console.log(formatStatusOutput(state));
16
+ return;
17
+ }
18
+ // action is a task status: todo, doing, done
19
+ const status = validateTaskStatus(action);
20
+ if (!taskDescription) {
21
+ throw new Error('Task description required');
22
+ }
23
+ const state = updateTaskStatus(sessionId, taskDescription, status);
24
+ console.log(formatStatusOutput(state));
25
+ }
@@ -0,0 +1 @@
1
+ export declare function tasksCommand(sessionId: string, action: string, taskDescriptions: string[]): void;
@@ -0,0 +1,9 @@
1
+ import { setTasks } from '../state.js';
2
+ import { formatStatusOutput } from '../output.js';
3
+ export function tasksCommand(sessionId, action, taskDescriptions) {
4
+ if (action !== 'set') {
5
+ throw new Error(`Unknown tasks action: ${action}. Use "tasks set <task1> <task2> ..."`);
6
+ }
7
+ const state = setTasks(sessionId, taskDescriptions);
8
+ console.log(formatStatusOutput(state));
9
+ }
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zerg-status - Progress reporting CLI for Zerg dashboard
4
+ *
5
+ * Usage:
6
+ * npx zerg-status init # Initialize session, outputs ZERG_SESSION:<id>
7
+ * npx zerg-status -s <id> state work # Set state to work/ask/done
8
+ * npx zerg-status -s <id> state ask "Question?" # Set state to ask with a question
9
+ * npx zerg-status -s <id> now "Headline" # Set current activity headline
10
+ * npx zerg-status -s <id> summary "Details" # Set detailed summary
11
+ * npx zerg-status -s <id> task add "Task" # Add a task
12
+ * npx zerg-status -s <id> task doing "Task" # Mark task as in progress
13
+ * npx zerg-status -s <id> task done "Task" # Mark task as complete
14
+ * npx zerg-status -s <id> task clear # Clear all tasks
15
+ * npx zerg-status -s <id> tasks set "A" "B" "C" # Replace all tasks
16
+ */
17
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * zerg-status - Progress reporting CLI for Zerg dashboard
4
+ *
5
+ * Usage:
6
+ * npx zerg-status init # Initialize session, outputs ZERG_SESSION:<id>
7
+ * npx zerg-status -s <id> state work # Set state to work/ask/done
8
+ * npx zerg-status -s <id> state ask "Question?" # Set state to ask with a question
9
+ * npx zerg-status -s <id> now "Headline" # Set current activity headline
10
+ * npx zerg-status -s <id> summary "Details" # Set detailed summary
11
+ * npx zerg-status -s <id> task add "Task" # Add a task
12
+ * npx zerg-status -s <id> task doing "Task" # Mark task as in progress
13
+ * npx zerg-status -s <id> task done "Task" # Mark task as complete
14
+ * npx zerg-status -s <id> task clear # Clear all tasks
15
+ * npx zerg-status -s <id> tasks set "A" "B" "C" # Replace all tasks
16
+ */
17
+ import { init } from './commands/init.js';
18
+ import { stateCommand } from './commands/state.js';
19
+ import { nowCommand } from './commands/now.js';
20
+ import { summaryCommand } from './commands/summary.js';
21
+ import { taskCommand } from './commands/task.js';
22
+ import { tasksCommand } from './commands/tasks.js';
23
+ import { validateSessionId, ValidationError } from './validate.js';
24
+ function parseArgs(argv) {
25
+ const args = argv.slice(2);
26
+ let sessionId;
27
+ let command = 'help';
28
+ const commandArgs = [];
29
+ let i = 0;
30
+ while (i < args.length) {
31
+ const arg = args[i];
32
+ if (arg === '-s' || arg === '--session') {
33
+ sessionId = args[i + 1];
34
+ i += 2;
35
+ continue;
36
+ }
37
+ if (!command || command === 'help') {
38
+ command = arg;
39
+ i++;
40
+ continue;
41
+ }
42
+ commandArgs.push(arg);
43
+ i++;
44
+ }
45
+ return { sessionId, command, args: commandArgs };
46
+ }
47
+ function printHelp() {
48
+ console.log(`
49
+ zerg-status - Progress reporting CLI for Zerg dashboard
50
+
51
+ COMMANDS:
52
+ init Initialize new session (outputs ZERG_SESSION:<id>)
53
+
54
+ -s <id> state <work|ask|done> Set session state
55
+ -s <id> state ask "Question?" Set state to ask with a question
56
+ -s <id> state done Set state to done
57
+
58
+ -s <id> now "Headline" Set current activity (max 50 chars)
59
+ -s <id> summary "Details" Set detailed summary (max 500 chars)
60
+
61
+ -s <id> task add "Task" Add a task (status: todo)
62
+ -s <id> task doing "Task" Mark task as in progress
63
+ -s <id> task done "Task" Mark task as complete
64
+ -s <id> task todo "Task" Mark task as pending
65
+ -s <id> task clear Clear all tasks
66
+
67
+ -s <id> tasks set "A" "B" "C" Replace all tasks (max 20 tasks)
68
+
69
+ EXAMPLES:
70
+ npx zerg-status init
71
+ # ZERG_SESSION:f7a3b2c1
72
+
73
+ npx zerg-status -s f7a3b2c1 state work
74
+ npx zerg-status -s f7a3b2c1 now "Writing unit tests"
75
+ npx zerg-status -s f7a3b2c1 tasks set "Task 1" "Task 2" "Task 3"
76
+ npx zerg-status -s f7a3b2c1 task doing "Task 1"
77
+ npx zerg-status -s f7a3b2c1 task done "Task 1"
78
+ npx zerg-status -s f7a3b2c1 state done
79
+
80
+ OUTPUT:
81
+ init outputs: ZERG_SESSION:<session_id>
82
+ all others: ZERG_STATUS:<full_json_state>
83
+ `);
84
+ }
85
+ function main() {
86
+ try {
87
+ const { sessionId, command, args } = parseArgs(process.argv);
88
+ switch (command) {
89
+ case 'init':
90
+ init();
91
+ break;
92
+ case 'state': {
93
+ const validSessionId = validateSessionId(sessionId);
94
+ const [stateValue, ...rest] = args;
95
+ if (!stateValue) {
96
+ throw new ValidationError('State value required. Use: work, ask, or done');
97
+ }
98
+ const question = stateValue === 'ask' ? rest.join(' ') : undefined;
99
+ stateCommand(validSessionId, stateValue, question);
100
+ break;
101
+ }
102
+ case 'now': {
103
+ const validSessionId = validateSessionId(sessionId);
104
+ const text = args.join(' ');
105
+ if (!text) {
106
+ throw new ValidationError('Now text required');
107
+ }
108
+ nowCommand(validSessionId, text);
109
+ break;
110
+ }
111
+ case 'summary': {
112
+ const validSessionId = validateSessionId(sessionId);
113
+ const text = args.join(' ');
114
+ if (!text) {
115
+ throw new ValidationError('Summary text required');
116
+ }
117
+ summaryCommand(validSessionId, text);
118
+ break;
119
+ }
120
+ case 'task': {
121
+ const validSessionId = validateSessionId(sessionId);
122
+ const [action, ...rest] = args;
123
+ if (!action) {
124
+ throw new ValidationError('Task action required. Use: add, doing, done, todo, or clear');
125
+ }
126
+ const taskDescription = rest.join(' ') || undefined;
127
+ taskCommand(validSessionId, action, taskDescription);
128
+ break;
129
+ }
130
+ case 'tasks': {
131
+ const validSessionId = validateSessionId(sessionId);
132
+ const [action, ...taskDescriptions] = args;
133
+ if (!action) {
134
+ throw new ValidationError('Tasks action required. Use: set');
135
+ }
136
+ tasksCommand(validSessionId, action, taskDescriptions);
137
+ break;
138
+ }
139
+ case 'help':
140
+ case '--help':
141
+ case '-h':
142
+ printHelp();
143
+ break;
144
+ default:
145
+ console.error(`Unknown command: ${command}`);
146
+ console.error('Run "npx zerg-status help" for usage');
147
+ process.exit(1);
148
+ }
149
+ }
150
+ catch (err) {
151
+ if (err instanceof ValidationError) {
152
+ console.error(`Error: ${err.message}`);
153
+ }
154
+ else if (err instanceof Error) {
155
+ console.error(`Error: ${err.message}`);
156
+ }
157
+ else {
158
+ console.error('Unknown error');
159
+ }
160
+ process.exit(1);
161
+ }
162
+ }
163
+ main();
@@ -0,0 +1,3 @@
1
+ import type { ZergState } from './types.js';
2
+ export declare function formatSessionOutput(sessionId: string): string;
3
+ export declare function formatStatusOutput(state: ZergState): string;
package/dist/output.js ADDED
@@ -0,0 +1,6 @@
1
+ export function formatSessionOutput(sessionId) {
2
+ return `ZERG_SESSION:${sessionId}`;
3
+ }
4
+ export function formatStatusOutput(state) {
5
+ return `ZERG_STATUS:${JSON.stringify(state)}`;
6
+ }
@@ -0,0 +1,13 @@
1
+ import type { ZergState, State, TaskStatus } from './types.js';
2
+ export declare function generateSessionId(): string;
3
+ export declare function createSession(): string;
4
+ export declare function loadState(sessionId: string): ZergState;
5
+ export declare function saveState(state: ZergState): void;
6
+ export declare function updateState(sessionId: string, updates: Partial<ZergState>): ZergState;
7
+ export declare function setState(sessionId: string, newState: State, question?: string): ZergState;
8
+ export declare function setNow(sessionId: string, now: string): ZergState;
9
+ export declare function setSummary(sessionId: string, summary: string): ZergState;
10
+ export declare function addTask(sessionId: string, taskDescription: string, status?: TaskStatus): ZergState;
11
+ export declare function updateTaskStatus(sessionId: string, taskDescription: string, status: TaskStatus): ZergState;
12
+ export declare function clearTasks(sessionId: string): ZergState;
13
+ export declare function setTasks(sessionId: string, taskDescriptions: string[]): ZergState;
package/dist/state.js ADDED
@@ -0,0 +1,114 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import crypto from 'crypto';
5
+ import { DEFAULT_STATE } from './types.js';
6
+ import { validateTaskCount, validateTask } from './validate.js';
7
+ const STATE_DIR = path.join(os.homedir(), '.zerg-status');
8
+ function ensureStateDir() {
9
+ if (!fs.existsSync(STATE_DIR)) {
10
+ fs.mkdirSync(STATE_DIR, { recursive: true });
11
+ }
12
+ }
13
+ function getStatePath(sessionId) {
14
+ return path.join(STATE_DIR, `${sessionId}.json`);
15
+ }
16
+ export function generateSessionId() {
17
+ return crypto.randomBytes(4).toString('hex');
18
+ }
19
+ export function createSession() {
20
+ ensureStateDir();
21
+ const sessionId = generateSessionId();
22
+ const state = {
23
+ session: sessionId,
24
+ ...DEFAULT_STATE,
25
+ };
26
+ fs.writeFileSync(getStatePath(sessionId), JSON.stringify(state, null, 2));
27
+ return sessionId;
28
+ }
29
+ export function loadState(sessionId) {
30
+ ensureStateDir();
31
+ const statePath = getStatePath(sessionId);
32
+ if (!fs.existsSync(statePath)) {
33
+ // Create default state for this session
34
+ const state = {
35
+ session: sessionId,
36
+ ...DEFAULT_STATE,
37
+ };
38
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
39
+ return state;
40
+ }
41
+ const data = fs.readFileSync(statePath, 'utf-8');
42
+ return JSON.parse(data);
43
+ }
44
+ export function saveState(state) {
45
+ ensureStateDir();
46
+ const statePath = getStatePath(state.session);
47
+ // Atomic write: write to temp file first, then rename
48
+ const tempPath = `${statePath}.tmp`;
49
+ fs.writeFileSync(tempPath, JSON.stringify(state, null, 2));
50
+ fs.renameSync(tempPath, statePath);
51
+ }
52
+ export function updateState(sessionId, updates) {
53
+ const state = loadState(sessionId);
54
+ Object.assign(state, updates);
55
+ saveState(state);
56
+ return state;
57
+ }
58
+ export function setState(sessionId, newState, question) {
59
+ const state = loadState(sessionId);
60
+ state.state = newState;
61
+ if (newState === 'ask' && question) {
62
+ state.question = question;
63
+ }
64
+ else if (newState !== 'ask') {
65
+ delete state.question;
66
+ }
67
+ saveState(state);
68
+ return state;
69
+ }
70
+ export function setNow(sessionId, now) {
71
+ return updateState(sessionId, { now });
72
+ }
73
+ export function setSummary(sessionId, summary) {
74
+ return updateState(sessionId, { summary });
75
+ }
76
+ export function addTask(sessionId, taskDescription, status = 'todo') {
77
+ const state = loadState(sessionId);
78
+ validateTask(taskDescription);
79
+ validateTaskCount(state.tasks.length + 1);
80
+ state.tasks.push({ task: taskDescription, status });
81
+ saveState(state);
82
+ return state;
83
+ }
84
+ export function updateTaskStatus(sessionId, taskDescription, status) {
85
+ const state = loadState(sessionId);
86
+ // Find task by exact match or partial match
87
+ let task = state.tasks.find(t => t.task === taskDescription);
88
+ if (!task) {
89
+ // Try partial match
90
+ task = state.tasks.find(t => t.task.toLowerCase().includes(taskDescription.toLowerCase()));
91
+ }
92
+ if (!task) {
93
+ // Add new task with the given status
94
+ validateTask(taskDescription);
95
+ validateTaskCount(state.tasks.length + 1);
96
+ state.tasks.push({ task: taskDescription, status });
97
+ }
98
+ else {
99
+ task.status = status;
100
+ }
101
+ saveState(state);
102
+ return state;
103
+ }
104
+ export function clearTasks(sessionId) {
105
+ return updateState(sessionId, { tasks: [] });
106
+ }
107
+ export function setTasks(sessionId, taskDescriptions) {
108
+ validateTaskCount(taskDescriptions.length);
109
+ const tasks = taskDescriptions.map(task => {
110
+ validateTask(task);
111
+ return { task, status: 'todo' };
112
+ });
113
+ return updateState(sessionId, { tasks });
114
+ }
@@ -0,0 +1,15 @@
1
+ export type State = 'work' | 'ask' | 'done';
2
+ export type TaskStatus = 'todo' | 'doing' | 'done';
3
+ export interface Task {
4
+ task: string;
5
+ status: TaskStatus;
6
+ }
7
+ export interface ZergState {
8
+ session: string;
9
+ state: State;
10
+ now: string;
11
+ summary: string;
12
+ tasks: Task[];
13
+ question?: string;
14
+ }
15
+ export declare const DEFAULT_STATE: Omit<ZergState, 'session'>;
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ export const DEFAULT_STATE = {
2
+ state: 'work',
3
+ now: '',
4
+ summary: '',
5
+ tasks: [],
6
+ };
@@ -0,0 +1,17 @@
1
+ import type { State, TaskStatus } from './types.js';
2
+ export declare const VALID_STATES: State[];
3
+ export declare const VALID_TASK_STATUSES: TaskStatus[];
4
+ export declare const MAX_NOW_LENGTH = 50;
5
+ export declare const MAX_SUMMARY_LENGTH = 500;
6
+ export declare const MAX_TASK_LENGTH = 200;
7
+ export declare const MAX_TASKS = 20;
8
+ export declare class ValidationError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ export declare function validateState(state: string): State;
12
+ export declare function validateTaskStatus(status: string): TaskStatus;
13
+ export declare function validateNow(now: string): string;
14
+ export declare function validateSummary(summary: string): string;
15
+ export declare function validateTask(task: string): string;
16
+ export declare function validateTaskCount(count: number): void;
17
+ export declare function validateSessionId(sessionId: string | undefined): string;
@@ -0,0 +1,53 @@
1
+ export const VALID_STATES = ['work', 'ask', 'done'];
2
+ export const VALID_TASK_STATUSES = ['todo', 'doing', 'done'];
3
+ export const MAX_NOW_LENGTH = 50;
4
+ export const MAX_SUMMARY_LENGTH = 500;
5
+ export const MAX_TASK_LENGTH = 200;
6
+ export const MAX_TASKS = 20;
7
+ export class ValidationError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'ValidationError';
11
+ }
12
+ }
13
+ export function validateState(state) {
14
+ if (!VALID_STATES.includes(state)) {
15
+ throw new ValidationError(`Invalid state: ${state}. Must be one of: ${VALID_STATES.join(', ')}`);
16
+ }
17
+ return state;
18
+ }
19
+ export function validateTaskStatus(status) {
20
+ if (!VALID_TASK_STATUSES.includes(status)) {
21
+ throw new ValidationError(`Invalid task status: ${status}. Must be one of: ${VALID_TASK_STATUSES.join(', ')}`);
22
+ }
23
+ return status;
24
+ }
25
+ export function validateNow(now) {
26
+ if (now.length > MAX_NOW_LENGTH) {
27
+ throw new ValidationError(`Now text too long (${now.length} chars). Max ${MAX_NOW_LENGTH} chars.`);
28
+ }
29
+ return now;
30
+ }
31
+ export function validateSummary(summary) {
32
+ if (summary.length > MAX_SUMMARY_LENGTH) {
33
+ throw new ValidationError(`Summary too long (${summary.length} chars). Max ${MAX_SUMMARY_LENGTH} chars.`);
34
+ }
35
+ return summary;
36
+ }
37
+ export function validateTask(task) {
38
+ if (task.length > MAX_TASK_LENGTH) {
39
+ throw new ValidationError(`Task too long (${task.length} chars). Max ${MAX_TASK_LENGTH} chars.`);
40
+ }
41
+ return task;
42
+ }
43
+ export function validateTaskCount(count) {
44
+ if (count > MAX_TASKS) {
45
+ throw new ValidationError(`Too many tasks (${count}). Max ${MAX_TASKS} tasks.`);
46
+ }
47
+ }
48
+ export function validateSessionId(sessionId) {
49
+ if (!sessionId) {
50
+ throw new ValidationError('Session ID required. Use -s <id> flag or run "npx zerg-status init" first.');
51
+ }
52
+ return sessionId;
53
+ }
package/package.json CHANGED
@@ -1,38 +1,27 @@
1
1
  {
2
2
  "name": "zerg-status",
3
- "version": "0.1.0",
4
- "description": "CLI for AI coding agents to report status to a central store",
3
+ "version": "1.0.0",
4
+ "description": "Progress reporting CLI for Zerg dashboard",
5
5
  "type": "module",
6
6
  "bin": {
7
- "zerg": "./dist/cli.js"
7
+ "zerg-status": "./bin/zerg-status.js"
8
8
  },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
9
11
  "files": [
12
+ "bin",
10
13
  "dist"
11
14
  ],
12
15
  "scripts": {
13
16
  "build": "tsc",
14
- "prepublishOnly": "npm run build"
15
- },
16
- "keywords": [
17
- "ai",
18
- "agent",
19
- "status",
20
- "cli",
21
- "devin",
22
- "claude",
23
- "coding-agent"
24
- ],
25
- "author": "Exa Labs",
26
- "license": "MIT",
27
- "repository": {
28
- "type": "git",
29
- "url": "https://github.com/exa-labs/zerg"
17
+ "dev": "tsc --watch",
18
+ "test": "node --test dist/*.test.js"
30
19
  },
31
20
  "engines": {
32
21
  "node": ">=18"
33
22
  },
34
23
  "devDependencies": {
35
- "@types/node": "^20.0.0",
24
+ "@types/node": "^20.10.0",
36
25
  "typescript": "^5.3.0"
37
26
  }
38
27
  }