sisyphi 1.1.8 → 1.1.9

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,68 @@
1
+ Welcome to Neovim!
2
+ ===================
3
+
4
+ You're looking at this file in nvim. Right now you're in NORMAL MODE.
5
+ That means keys are commands, not text. Let's learn the basics.
6
+
7
+
8
+ --- MOVING AROUND (Normal Mode) ---
9
+
10
+ Try these now:
11
+ gg Jump to the top of this file
12
+ G Jump to the bottom of this file
13
+ Ctrl+u Scroll up half a page
14
+ Ctrl+d Scroll down half a page
15
+ /Welcome Search for "Welcome" (press Enter, then n for next match)
16
+
17
+ Arrow keys work too, but h/j/k/l are the vim way:
18
+ h = left j = down k = up l = right
19
+
20
+
21
+ --- ENTERING INSERT MODE ---
22
+
23
+ Press i right now. You should see -- INSERT -- at the bottom.
24
+ Now you can type normally! Try typing something on the line below:
25
+
26
+ > [type here]
27
+
28
+ Press Esc when you're done to go back to normal mode.
29
+
30
+ Other ways to enter insert mode:
31
+ i Insert at cursor
32
+ a Insert after cursor
33
+ o Open new line below and insert
34
+ O Open new line above and insert
35
+
36
+
37
+ --- PRACTICE: EDIT THIS SECTION ---
38
+
39
+ Fix the typos below (hint: navigate to the typo, press i, fix it, press Esc):
40
+
41
+ 1. The quikc brown fox jumps over the lazy dog.
42
+ 2. Sisyphus is a multi-agnet orchestrator.
43
+ 3. Tmux lets you spilt your terminal into panes.
44
+
45
+
46
+ --- SAVING AND QUITTING ---
47
+
48
+ You're almost done! Here's how to get out:
49
+
50
+ :w Enter Save the file (write)
51
+ :q Enter Quit (fails if unsaved changes)
52
+ :wq Enter Save and quit
53
+ :q! Enter Quit WITHOUT saving
54
+ ZZ Shortcut for save and quit (Shift+z twice)
55
+
56
+ Try it now: type :wq and press Enter to save your changes and close nvim.
57
+ The pane will close automatically and you'll be back in Claude.
58
+
59
+
60
+ --- BONUS (optional) ---
61
+
62
+ u Undo last change
63
+ Ctrl+r Redo
64
+ dd Delete entire line
65
+ yy Copy (yank) entire line
66
+ p Paste below cursor
67
+ :123 Jump to line 123
68
+ * Search for the word under cursor
@@ -0,0 +1,14 @@
1
+ # Todo App
2
+
3
+ A minimal Node.js todo API. No dependencies — just `node:http` and `node:test`.
4
+
5
+ ## Commands
6
+ ```bash
7
+ node server.js # Start the API on port 3456
8
+ node --test test.js # Run tests
9
+ ```
10
+
11
+ ## Structure
12
+ - `todo.js` — In-memory todo store (add, list, get, toggle, delete)
13
+ - `server.js` — HTTP API server
14
+ - `test.js` — Unit tests for the store
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "sisyphus-todo-demo",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "A minimal todo app — sisyphus tutorial demo",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "test": "node --test test.js"
9
+ }
10
+ }
@@ -0,0 +1,76 @@
1
+ import { createServer } from 'node:http';
2
+ import { addTodo, listTodos, getTodo, toggleTodo, deleteTodo } from './todo.js';
3
+
4
+ const PORT = 3456;
5
+
6
+ function parseBody(req) {
7
+ return new Promise((resolve) => {
8
+ let body = '';
9
+ req.on('data', (chunk) => (body += chunk));
10
+ req.on('end', () => {
11
+ try { resolve(JSON.parse(body)); }
12
+ catch { resolve({}); }
13
+ });
14
+ });
15
+ }
16
+
17
+ function json(res, status, data) {
18
+ res.writeHead(status, { 'Content-Type': 'application/json' });
19
+ res.end(JSON.stringify(data, null, 2));
20
+ }
21
+
22
+ const server = createServer(async (req, res) => {
23
+ const url = new URL(req.url, `http://localhost:${PORT}`);
24
+ const path = url.pathname;
25
+
26
+ // GET /todos
27
+ if (req.method === 'GET' && path === '/todos') {
28
+ return json(res, 200, listTodos());
29
+ }
30
+
31
+ // POST /todos
32
+ if (req.method === 'POST' && path === '/todos') {
33
+ const { title } = await parseBody(req);
34
+ if (!title) return json(res, 400, { error: 'title is required' });
35
+ return json(res, 201, addTodo(title));
36
+ }
37
+
38
+ // GET /todos/:id
39
+ const match = path.match(/^\/todos\/(\d+)$/);
40
+ if (match) {
41
+ const id = parseInt(match[1]);
42
+ if (req.method === 'GET') {
43
+ const todo = getTodo(id);
44
+ return todo ? json(res, 200, todo) : json(res, 404, { error: 'not found' });
45
+ }
46
+ // PATCH /todos/:id/toggle
47
+ if (req.method === 'PATCH' && path.endsWith('/toggle')) {
48
+ // BUG: this never matches because the regex above doesn't include /toggle
49
+ }
50
+ // DELETE /todos/:id
51
+ if (req.method === 'DELETE') {
52
+ return deleteTodo(id) ? json(res, 200, { deleted: true }) : json(res, 404, { error: 'not found' });
53
+ }
54
+ }
55
+
56
+ // PATCH /todos/:id/toggle
57
+ const toggleMatch = path.match(/^\/todos\/(\d+)\/toggle$/);
58
+ if (req.method === 'PATCH' && toggleMatch) {
59
+ const id = parseInt(toggleMatch[1]);
60
+ const todo = toggleTodo(id);
61
+ return todo ? json(res, 200, todo) : json(res, 404, { error: 'not found' });
62
+ }
63
+
64
+ json(res, 404, { error: 'not found' });
65
+ });
66
+
67
+ server.listen(PORT, () => {
68
+ console.log(`Todo API running at http://localhost:${PORT}`);
69
+ console.log('');
70
+ console.log('Endpoints:');
71
+ console.log(' GET /todos List all todos');
72
+ console.log(' POST /todos Create a todo (body: {"title": "..."})');
73
+ console.log(' GET /todos/:id Get a todo');
74
+ console.log(' PATCH /todos/:id/toggle Toggle done');
75
+ console.log(' DELETE /todos/:id Delete a todo');
76
+ });
@@ -0,0 +1,48 @@
1
+ import { describe, it, beforeEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { addTodo, listTodos, getTodo, toggleTodo, deleteTodo, clearAll } from './todo.js';
4
+
5
+ describe('todo store', () => {
6
+ beforeEach(() => clearAll());
7
+
8
+ it('adds a todo', () => {
9
+ const todo = addTodo('Buy milk');
10
+ assert.strictEqual(todo.title, 'Buy milk');
11
+ assert.strictEqual(todo.done, false);
12
+ assert.ok(todo.id);
13
+ });
14
+
15
+ it('lists todos', () => {
16
+ addTodo('First');
17
+ addTodo('Second');
18
+ assert.strictEqual(listTodos().length, 2);
19
+ });
20
+
21
+ it('gets a todo by id', () => {
22
+ const todo = addTodo('Find me');
23
+ assert.deepStrictEqual(getTodo(todo.id), todo);
24
+ });
25
+
26
+ it('returns null for missing todo', () => {
27
+ assert.strictEqual(getTodo(999), null);
28
+ });
29
+
30
+ it('toggles done status', () => {
31
+ const todo = addTodo('Toggle me');
32
+ assert.strictEqual(todo.done, false);
33
+ toggleTodo(todo.id);
34
+ assert.strictEqual(todo.done, true);
35
+ toggleTodo(todo.id);
36
+ assert.strictEqual(todo.done, false);
37
+ });
38
+
39
+ it('deletes a todo', () => {
40
+ const todo = addTodo('Delete me');
41
+ assert.strictEqual(deleteTodo(todo.id), true);
42
+ assert.strictEqual(listTodos().length, 0);
43
+ });
44
+
45
+ it('returns false when deleting nonexistent', () => {
46
+ assert.strictEqual(deleteTodo(999), false);
47
+ });
48
+ });
@@ -0,0 +1,35 @@
1
+ // In-memory todo store
2
+ const todos = [];
3
+ let nextId = 1;
4
+
5
+ export function addTodo(title) {
6
+ const todo = { id: nextId++, title, done: false, createdAt: new Date().toISOString() };
7
+ todos.push(todo);
8
+ return todo;
9
+ }
10
+
11
+ export function listTodos() {
12
+ return [...todos];
13
+ }
14
+
15
+ export function getTodo(id) {
16
+ return todos.find((t) => t.id === id) || null;
17
+ }
18
+
19
+ export function toggleTodo(id) {
20
+ const todo = getTodo(id);
21
+ if (todo) todo.done = !todo.done;
22
+ return todo;
23
+ }
24
+
25
+ export function deleteTodo(id) {
26
+ const idx = todos.findIndex((t) => t.id === id);
27
+ if (idx === -1) return false;
28
+ todos.splice(idx, 1);
29
+ return true;
30
+ }
31
+
32
+ export function clearAll() {
33
+ todos.length = 0;
34
+ nextId = 1;
35
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sisyphi",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "tmux-integrated orchestration daemon for Claude Code multi-agent workflows",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,68 @@
1
+ Welcome to Neovim!
2
+ ===================
3
+
4
+ You're looking at this file in nvim. Right now you're in NORMAL MODE.
5
+ That means keys are commands, not text. Let's learn the basics.
6
+
7
+
8
+ --- MOVING AROUND (Normal Mode) ---
9
+
10
+ Try these now:
11
+ gg Jump to the top of this file
12
+ G Jump to the bottom of this file
13
+ Ctrl+u Scroll up half a page
14
+ Ctrl+d Scroll down half a page
15
+ /Welcome Search for "Welcome" (press Enter, then n for next match)
16
+
17
+ Arrow keys work too, but h/j/k/l are the vim way:
18
+ h = left j = down k = up l = right
19
+
20
+
21
+ --- ENTERING INSERT MODE ---
22
+
23
+ Press i right now. You should see -- INSERT -- at the bottom.
24
+ Now you can type normally! Try typing something on the line below:
25
+
26
+ > [type here]
27
+
28
+ Press Esc when you're done to go back to normal mode.
29
+
30
+ Other ways to enter insert mode:
31
+ i Insert at cursor
32
+ a Insert after cursor
33
+ o Open new line below and insert
34
+ O Open new line above and insert
35
+
36
+
37
+ --- PRACTICE: EDIT THIS SECTION ---
38
+
39
+ Fix the typos below (hint: navigate to the typo, press i, fix it, press Esc):
40
+
41
+ 1. The quikc brown fox jumps over the lazy dog.
42
+ 2. Sisyphus is a multi-agnet orchestrator.
43
+ 3. Tmux lets you spilt your terminal into panes.
44
+
45
+
46
+ --- SAVING AND QUITTING ---
47
+
48
+ You're almost done! Here's how to get out:
49
+
50
+ :w Enter Save the file (write)
51
+ :q Enter Quit (fails if unsaved changes)
52
+ :wq Enter Save and quit
53
+ :q! Enter Quit WITHOUT saving
54
+ ZZ Shortcut for save and quit (Shift+z twice)
55
+
56
+ Try it now: type :wq and press Enter to save your changes and close nvim.
57
+ The pane will close automatically and you'll be back in Claude.
58
+
59
+
60
+ --- BONUS (optional) ---
61
+
62
+ u Undo last change
63
+ Ctrl+r Redo
64
+ dd Delete entire line
65
+ yy Copy (yank) entire line
66
+ p Paste below cursor
67
+ :123 Jump to line 123
68
+ * Search for the word under cursor
@@ -0,0 +1,14 @@
1
+ # Todo App
2
+
3
+ A minimal Node.js todo API. No dependencies — just `node:http` and `node:test`.
4
+
5
+ ## Commands
6
+ ```bash
7
+ node server.js # Start the API on port 3456
8
+ node --test test.js # Run tests
9
+ ```
10
+
11
+ ## Structure
12
+ - `todo.js` — In-memory todo store (add, list, get, toggle, delete)
13
+ - `server.js` — HTTP API server
14
+ - `test.js` — Unit tests for the store
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "sisyphus-todo-demo",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "A minimal todo app — sisyphus tutorial demo",
6
+ "scripts": {
7
+ "start": "node server.js",
8
+ "test": "node --test test.js"
9
+ }
10
+ }
@@ -0,0 +1,76 @@
1
+ import { createServer } from 'node:http';
2
+ import { addTodo, listTodos, getTodo, toggleTodo, deleteTodo } from './todo.js';
3
+
4
+ const PORT = 3456;
5
+
6
+ function parseBody(req) {
7
+ return new Promise((resolve) => {
8
+ let body = '';
9
+ req.on('data', (chunk) => (body += chunk));
10
+ req.on('end', () => {
11
+ try { resolve(JSON.parse(body)); }
12
+ catch { resolve({}); }
13
+ });
14
+ });
15
+ }
16
+
17
+ function json(res, status, data) {
18
+ res.writeHead(status, { 'Content-Type': 'application/json' });
19
+ res.end(JSON.stringify(data, null, 2));
20
+ }
21
+
22
+ const server = createServer(async (req, res) => {
23
+ const url = new URL(req.url, `http://localhost:${PORT}`);
24
+ const path = url.pathname;
25
+
26
+ // GET /todos
27
+ if (req.method === 'GET' && path === '/todos') {
28
+ return json(res, 200, listTodos());
29
+ }
30
+
31
+ // POST /todos
32
+ if (req.method === 'POST' && path === '/todos') {
33
+ const { title } = await parseBody(req);
34
+ if (!title) return json(res, 400, { error: 'title is required' });
35
+ return json(res, 201, addTodo(title));
36
+ }
37
+
38
+ // GET /todos/:id
39
+ const match = path.match(/^\/todos\/(\d+)$/);
40
+ if (match) {
41
+ const id = parseInt(match[1]);
42
+ if (req.method === 'GET') {
43
+ const todo = getTodo(id);
44
+ return todo ? json(res, 200, todo) : json(res, 404, { error: 'not found' });
45
+ }
46
+ // PATCH /todos/:id/toggle
47
+ if (req.method === 'PATCH' && path.endsWith('/toggle')) {
48
+ // BUG: this never matches because the regex above doesn't include /toggle
49
+ }
50
+ // DELETE /todos/:id
51
+ if (req.method === 'DELETE') {
52
+ return deleteTodo(id) ? json(res, 200, { deleted: true }) : json(res, 404, { error: 'not found' });
53
+ }
54
+ }
55
+
56
+ // PATCH /todos/:id/toggle
57
+ const toggleMatch = path.match(/^\/todos\/(\d+)\/toggle$/);
58
+ if (req.method === 'PATCH' && toggleMatch) {
59
+ const id = parseInt(toggleMatch[1]);
60
+ const todo = toggleTodo(id);
61
+ return todo ? json(res, 200, todo) : json(res, 404, { error: 'not found' });
62
+ }
63
+
64
+ json(res, 404, { error: 'not found' });
65
+ });
66
+
67
+ server.listen(PORT, () => {
68
+ console.log(`Todo API running at http://localhost:${PORT}`);
69
+ console.log('');
70
+ console.log('Endpoints:');
71
+ console.log(' GET /todos List all todos');
72
+ console.log(' POST /todos Create a todo (body: {"title": "..."})');
73
+ console.log(' GET /todos/:id Get a todo');
74
+ console.log(' PATCH /todos/:id/toggle Toggle done');
75
+ console.log(' DELETE /todos/:id Delete a todo');
76
+ });
@@ -0,0 +1,48 @@
1
+ import { describe, it, beforeEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { addTodo, listTodos, getTodo, toggleTodo, deleteTodo, clearAll } from './todo.js';
4
+
5
+ describe('todo store', () => {
6
+ beforeEach(() => clearAll());
7
+
8
+ it('adds a todo', () => {
9
+ const todo = addTodo('Buy milk');
10
+ assert.strictEqual(todo.title, 'Buy milk');
11
+ assert.strictEqual(todo.done, false);
12
+ assert.ok(todo.id);
13
+ });
14
+
15
+ it('lists todos', () => {
16
+ addTodo('First');
17
+ addTodo('Second');
18
+ assert.strictEqual(listTodos().length, 2);
19
+ });
20
+
21
+ it('gets a todo by id', () => {
22
+ const todo = addTodo('Find me');
23
+ assert.deepStrictEqual(getTodo(todo.id), todo);
24
+ });
25
+
26
+ it('returns null for missing todo', () => {
27
+ assert.strictEqual(getTodo(999), null);
28
+ });
29
+
30
+ it('toggles done status', () => {
31
+ const todo = addTodo('Toggle me');
32
+ assert.strictEqual(todo.done, false);
33
+ toggleTodo(todo.id);
34
+ assert.strictEqual(todo.done, true);
35
+ toggleTodo(todo.id);
36
+ assert.strictEqual(todo.done, false);
37
+ });
38
+
39
+ it('deletes a todo', () => {
40
+ const todo = addTodo('Delete me');
41
+ assert.strictEqual(deleteTodo(todo.id), true);
42
+ assert.strictEqual(listTodos().length, 0);
43
+ });
44
+
45
+ it('returns false when deleting nonexistent', () => {
46
+ assert.strictEqual(deleteTodo(999), false);
47
+ });
48
+ });
@@ -0,0 +1,35 @@
1
+ // In-memory todo store
2
+ const todos = [];
3
+ let nextId = 1;
4
+
5
+ export function addTodo(title) {
6
+ const todo = { id: nextId++, title, done: false, createdAt: new Date().toISOString() };
7
+ todos.push(todo);
8
+ return todo;
9
+ }
10
+
11
+ export function listTodos() {
12
+ return [...todos];
13
+ }
14
+
15
+ export function getTodo(id) {
16
+ return todos.find((t) => t.id === id) || null;
17
+ }
18
+
19
+ export function toggleTodo(id) {
20
+ const todo = getTodo(id);
21
+ if (todo) todo.done = !todo.done;
22
+ return todo;
23
+ }
24
+
25
+ export function deleteTodo(id) {
26
+ const idx = todos.findIndex((t) => t.id === id);
27
+ if (idx === -1) return false;
28
+ todos.splice(idx, 1);
29
+ return true;
30
+ }
31
+
32
+ export function clearAll() {
33
+ todos.length = 0;
34
+ nextId = 1;
35
+ }