@tagma/sdk 0.1.3 → 0.1.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.
package/README.md CHANGED
@@ -1,139 +1,139 @@
1
- # @tagma/sdk
2
-
3
- A local AI task orchestration SDK for [Bun](https://bun.sh). Define multi-track pipelines in YAML, run AI coding agents (Claude Code, Codex, OpenCode) and shell commands in parallel with dependency resolution, approval gates, and lifecycle hooks.
4
-
5
- ## Install
6
-
7
- ```bash
8
- bun add @tagma/sdk
9
- ```
10
-
11
- ## Quick Start
12
-
13
- **1. Define a pipeline** (`pipeline.yaml`)
14
-
15
- ```yaml
16
- pipeline:
17
- name: build-and-test
18
- tracks:
19
- - id: backend
20
- name: Backend
21
- driver: claude-code
22
- permissions: { read: true, write: true, execute: false }
23
- tasks:
24
- - id: implement
25
- name: Implement feature
26
- prompt: "Add a /health endpoint to src/server.ts"
27
- output: ./output/implement.txt
28
- - id: test
29
- name: Run tests
30
- command: "bun test"
31
- depends_on: [implement]
32
- ```
33
-
34
- **2. Run it programmatically**
35
-
36
- ```ts
37
- import {
38
- bootstrapBuiltins,
39
- loadPipeline,
40
- runPipeline,
41
- InMemoryApprovalGateway,
42
- } from '@tagma/sdk';
43
-
44
- // Register built-in drivers, triggers, completions
45
- bootstrapBuiltins();
46
-
47
- const yaml = await Bun.file('pipeline.yaml').text();
48
- const config = await loadPipeline(yaml, process.cwd());
49
-
50
- const result = await runPipeline(config, process.cwd());
51
- console.log(result.success ? 'Done' : 'Failed');
52
- ```
53
-
54
- ## Features
55
-
56
- - **Multi-track DAG execution** -- tasks run in parallel across tracks, respecting `depends_on` ordering
57
- - **Driver plugins** -- built-in `claude-code` driver; install `@tagma/driver-codex` or `@tagma/driver-opencode` for other agents
58
- - **Session handoff** -- `continue_from` passes context between tasks (session resume or text injection)
59
- - **Approval gates** -- trigger-based approval with stdin and WebSocket adapters
60
- - **Lifecycle hooks** -- `pipeline_start`, `task_start`, `task_success`, `task_failure`, `pipeline_complete`, `pipeline_error`
61
- - **Middleware** -- enrich prompts before execution (e.g. inject static context)
62
- - **Completion checks** -- validate task output with `exit_code`, `file_exists`, or `output_check` plugins
63
- - **Template expansion** -- reusable task templates with parameterized `use` / `with`
64
-
65
- ## Pipeline YAML Reference
66
-
67
- ```yaml
68
- pipeline:
69
- name: my-pipeline
70
- driver: claude-code # default driver for all tasks
71
- timeout: 30m # pipeline-level timeout
72
- plugins: # load external driver plugins
73
- - "@tagma/driver-codex"
74
- hooks:
75
- pipeline_start: "echo starting"
76
- task_failure: "notify-slack.sh"
77
- tracks:
78
- - id: track-1
79
- name: Track One
80
- model_tier: high # high | medium | low
81
- permissions:
82
- read: true
83
- write: true
84
- execute: false
85
- on_failure: skip_downstream # skip_downstream | stop_all | ignore
86
- tasks:
87
- - id: task-a
88
- name: Do something
89
- prompt: "Your prompt here"
90
- output: ./output/task-a.txt
91
- timeout: 10m
92
- - id: task-b
93
- name: Follow up
94
- prompt: "Continue the work"
95
- continue_from: task-a
96
- depends_on: [task-a]
97
- ```
98
-
99
- ## API
100
-
101
- ### `bootstrapBuiltins()`
102
-
103
- Registers all built-in plugins (claude-code driver, file/manual triggers, completion checks, static-context middleware).
104
-
105
- ### `loadPipeline(yaml: string, workDir: string): Promise<PipelineConfig>`
106
-
107
- Parses YAML, resolves inheritance, expands templates, and validates the configuration.
108
-
109
- ### `runPipeline(config, workDir, options?): Promise<EngineResult>`
110
-
111
- Executes the pipeline. Returns `{ success, summary, states }`.
112
-
113
- Options:
114
- - `approvalGateway` -- custom `ApprovalGateway` instance (defaults to `InMemoryApprovalGateway`)
115
-
116
- ### `loadPlugins(names: string[]): Promise<void>`
117
-
118
- Dynamically loads and registers external plugin packages.
119
-
120
- ### `attachStdinApprovalAdapter(gateway): StdinApprovalAdapter`
121
-
122
- Attaches an interactive stdin-based approval handler.
123
-
124
- ### `attachWebSocketApprovalAdapter(gateway, options?): WebSocketApprovalAdapter`
125
-
126
- Starts a WebSocket server for remote approval decisions.
127
-
128
- ## Related Packages
129
-
130
- | Package | Description |
131
- |---|---|
132
- | [@tagma/types](https://www.npmjs.com/package/@tagma/types) | Shared TypeScript types |
133
- | [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
134
- | [@tagma/driver-opencode](https://www.npmjs.com/package/@tagma/driver-opencode) | OpenCode CLI driver plugin |
135
- | [@tagma/cli](https://www.npmjs.com/package/@tagma/cli) | CLI runner |
136
-
137
- ## License
138
-
139
- MIT
1
+ # @tagma/sdk
2
+
3
+ A local AI task orchestration SDK for [Bun](https://bun.sh). Define multi-track pipelines in YAML, run AI coding agents (Claude Code, Codex, OpenCode) and shell commands in parallel with dependency resolution, approval gates, and lifecycle hooks.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @tagma/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ **1. Define a pipeline** (`pipeline.yaml`)
14
+
15
+ ```yaml
16
+ pipeline:
17
+ name: build-and-test
18
+ tracks:
19
+ - id: backend
20
+ name: Backend
21
+ driver: claude-code
22
+ permissions: { read: true, write: true, execute: false }
23
+ tasks:
24
+ - id: implement
25
+ name: Implement feature
26
+ prompt: "Add a /health endpoint to src/server.ts"
27
+ output: ./output/implement.txt
28
+ - id: test
29
+ name: Run tests
30
+ command: "bun test"
31
+ depends_on: [implement]
32
+ ```
33
+
34
+ **2. Run it programmatically**
35
+
36
+ ```ts
37
+ import {
38
+ bootstrapBuiltins,
39
+ loadPipeline,
40
+ runPipeline,
41
+ InMemoryApprovalGateway,
42
+ } from '@tagma/sdk';
43
+
44
+ // Register built-in drivers, triggers, completions
45
+ bootstrapBuiltins();
46
+
47
+ const yaml = await Bun.file('pipeline.yaml').text();
48
+ const config = await loadPipeline(yaml, process.cwd());
49
+
50
+ const result = await runPipeline(config, process.cwd());
51
+ console.log(result.success ? 'Done' : 'Failed');
52
+ ```
53
+
54
+ ## Features
55
+
56
+ - **Multi-track DAG execution** -- tasks run in parallel across tracks, respecting `depends_on` ordering
57
+ - **Driver plugins** -- built-in `claude-code` driver; install `@tagma/driver-codex` or `@tagma/driver-opencode` for other agents
58
+ - **Session handoff** -- `continue_from` passes context between tasks (session resume or text injection)
59
+ - **Approval gates** -- trigger-based approval with stdin and WebSocket adapters
60
+ - **Lifecycle hooks** -- `pipeline_start`, `task_start`, `task_success`, `task_failure`, `pipeline_complete`, `pipeline_error`
61
+ - **Middleware** -- enrich prompts before execution (e.g. inject static context)
62
+ - **Completion checks** -- validate task output with `exit_code`, `file_exists`, or `output_check` plugins
63
+ - **Template expansion** -- reusable task templates with parameterized `use` / `with`
64
+
65
+ ## Pipeline YAML Reference
66
+
67
+ ```yaml
68
+ pipeline:
69
+ name: my-pipeline
70
+ driver: claude-code # default driver for all tasks
71
+ timeout: 30m # pipeline-level timeout
72
+ plugins: # load external driver plugins
73
+ - "@tagma/driver-codex"
74
+ hooks:
75
+ pipeline_start: "echo starting"
76
+ task_failure: "notify-slack.sh"
77
+ tracks:
78
+ - id: track-1
79
+ name: Track One
80
+ model_tier: high # high | medium | low
81
+ permissions:
82
+ read: true
83
+ write: true
84
+ execute: false
85
+ on_failure: skip_downstream # skip_downstream | stop_all | ignore
86
+ tasks:
87
+ - id: task-a
88
+ name: Do something
89
+ prompt: "Your prompt here"
90
+ output: ./output/task-a.txt
91
+ timeout: 10m
92
+ - id: task-b
93
+ name: Follow up
94
+ prompt: "Continue the work"
95
+ continue_from: task-a
96
+ depends_on: [task-a]
97
+ ```
98
+
99
+ ## API
100
+
101
+ ### `bootstrapBuiltins()`
102
+
103
+ Registers all built-in plugins (claude-code driver, file/manual triggers, completion checks, static-context middleware).
104
+
105
+ ### `loadPipeline(yaml: string, workDir: string): Promise<PipelineConfig>`
106
+
107
+ Parses YAML, resolves inheritance, expands templates, and validates the configuration.
108
+
109
+ ### `runPipeline(config, workDir, options?): Promise<EngineResult>`
110
+
111
+ Executes the pipeline. Returns `{ success, summary, states }`.
112
+
113
+ Options:
114
+ - `approvalGateway` -- custom `ApprovalGateway` instance (defaults to `InMemoryApprovalGateway`)
115
+
116
+ ### `loadPlugins(names: string[]): Promise<void>`
117
+
118
+ Dynamically loads and registers external plugin packages.
119
+
120
+ ### `attachStdinApprovalAdapter(gateway): StdinApprovalAdapter`
121
+
122
+ Attaches an interactive stdin-based approval handler.
123
+
124
+ ### `attachWebSocketApprovalAdapter(gateway, options?): WebSocketApprovalAdapter`
125
+
126
+ Starts a WebSocket server for remote approval decisions.
127
+
128
+ ## Related Packages
129
+
130
+ | Package | Description |
131
+ |---|---|
132
+ | [@tagma/types](https://www.npmjs.com/package/@tagma/types) | Shared TypeScript types |
133
+ | [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
134
+ | [@tagma/driver-opencode](https://www.npmjs.com/package/@tagma/driver-opencode) | OpenCode CLI driver plugin |
135
+ | [@tagma/cli](https://www.npmjs.com/package/@tagma/cli) | CLI runner |
136
+
137
+ ## License
138
+
139
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagma/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  "plugins/*"
@@ -20,13 +20,13 @@
20
20
  "dependencies": {
21
21
  "js-yaml": "^4.1.0",
22
22
  "chokidar": "^4.0.0",
23
- "@tagma/types": "0.1.2"
23
+ "@tagma/types": "workspace:*"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/js-yaml": "^4.0.9",
27
27
  "bun-types": "latest",
28
28
  "typescript": "^6.0.2",
29
- "@tagma/driver-codex": "0.1.2",
30
- "@tagma/driver-opencode": "0.1.2"
29
+ "@tagma/driver-codex": "workspace:*",
30
+ "@tagma/driver-opencode": "workspace:*"
31
31
  }
32
32
  }
@@ -1,117 +1,117 @@
1
- import * as readline from 'readline';
2
- import type { ApprovalGateway, ApprovalRequest } from '../approval';
3
-
4
- // ═══ CLI Stdin Adapter ═══
5
- //
6
- // Subscribes to the gateway's 'requested' events, prompts the user on stdout,
7
- // reads a line from stdin, and calls gateway.resolve(). Handles at most one
8
- // prompt at a time — additional requests queue up.
9
-
10
- export interface StdinApprovalAdapter {
11
- readonly detach: () => void;
12
- }
13
-
14
- export function attachStdinApprovalAdapter(gateway: ApprovalGateway): StdinApprovalAdapter {
15
- const queue: ApprovalRequest[] = [];
16
- let processing = false;
17
- let rl: readline.Interface | null = null;
18
-
19
- function ensureReadline(): readline.Interface {
20
- if (!rl) {
21
- rl = readline.createInterface({ input: process.stdin, terminal: false });
22
- }
23
- return rl;
24
- }
25
-
26
- function readOneLine(): Promise<string> {
27
- return new Promise((resolvePromise) => {
28
- const reader = ensureReadline();
29
- const handler = (line: string): void => {
30
- reader.off('line', handler);
31
- resolvePromise(line);
32
- };
33
- reader.on('line', handler);
34
- });
35
- }
36
-
37
- async function processNext(): Promise<void> {
38
- if (processing) return;
39
- processing = true;
40
- try {
41
- while (queue.length > 0) {
42
- const req = queue.shift()!;
43
- // If the request was already resolved by another path while queued, skip it.
44
- if (!gateway.pending().some((p) => p.id === req.id)) continue;
45
-
46
- const optionsStr = req.options.join(' / ');
47
- process.stdout.write(
48
- `\n[APPROVAL REQUIRED] ${req.message}\n` +
49
- ` id: ${req.id}\n` +
50
- ` task: ${req.taskId}${req.trackId ? ` (track: ${req.trackId})` : ''}\n` +
51
- ` options: ${optionsStr}\n` +
52
- ` > `,
53
- );
54
-
55
- const input = (await readOneLine()).trim().toLowerCase();
56
-
57
- const approveAliases = new Set(['approve', 'yes', 'y', 'ok', 'true', '1']);
58
- const rejectAliases = new Set(['reject', 'no', 'n', 'deny', 'false', '0']);
59
- const matchedOption = req.options.find((o) => o.toLowerCase() === input);
60
-
61
- if (matchedOption) {
62
- const isReject = rejectAliases.has(matchedOption.toLowerCase());
63
- gateway.resolve(req.id, {
64
- outcome: isReject ? 'rejected' : 'approved',
65
- choice: matchedOption,
66
- actor: 'cli',
67
- });
68
- } else if (approveAliases.has(input)) {
69
- gateway.resolve(req.id, { outcome: 'approved', choice: input, actor: 'cli' });
70
- } else if (rejectAliases.has(input)) {
71
- gateway.resolve(req.id, {
72
- outcome: 'rejected',
73
- choice: input,
74
- actor: 'cli',
75
- reason: 'user rejected via CLI',
76
- });
77
- } else {
78
- process.stdout.write(` unrecognized input "${input}" — treating as rejection\n`);
79
- gateway.resolve(req.id, {
80
- outcome: 'rejected',
81
- actor: 'cli',
82
- reason: `unrecognized CLI input: ${input}`,
83
- });
84
- }
85
- }
86
- } finally {
87
- processing = false;
88
- }
89
- }
90
-
91
- const unsubscribe = gateway.subscribe((event) => {
92
- switch (event.type) {
93
- case 'requested':
94
- queue.push(event.request);
95
- void processNext();
96
- return;
97
- case 'resolved':
98
- case 'expired':
99
- case 'aborted': {
100
- // Drop from queue if it's still waiting its turn.
101
- const idx = queue.findIndex((r) => r.id === event.request.id);
102
- if (idx >= 0) queue.splice(idx, 1);
103
- return;
104
- }
105
- }
106
- });
107
-
108
- return {
109
- detach: () => {
110
- unsubscribe();
111
- if (rl) {
112
- rl.close();
113
- rl = null;
114
- }
115
- },
116
- };
117
- }
1
+ import * as readline from 'readline';
2
+ import type { ApprovalGateway, ApprovalRequest } from '../approval';
3
+
4
+ // ═══ CLI Stdin Adapter ═══
5
+ //
6
+ // Subscribes to the gateway's 'requested' events, prompts the user on stdout,
7
+ // reads a line from stdin, and calls gateway.resolve(). Handles at most one
8
+ // prompt at a time — additional requests queue up.
9
+
10
+ export interface StdinApprovalAdapter {
11
+ readonly detach: () => void;
12
+ }
13
+
14
+ export function attachStdinApprovalAdapter(gateway: ApprovalGateway): StdinApprovalAdapter {
15
+ const queue: ApprovalRequest[] = [];
16
+ let processing = false;
17
+ let rl: readline.Interface | null = null;
18
+
19
+ function ensureReadline(): readline.Interface {
20
+ if (!rl) {
21
+ rl = readline.createInterface({ input: process.stdin, terminal: false });
22
+ }
23
+ return rl;
24
+ }
25
+
26
+ function readOneLine(): Promise<string> {
27
+ return new Promise((resolvePromise) => {
28
+ const reader = ensureReadline();
29
+ const handler = (line: string): void => {
30
+ reader.off('line', handler);
31
+ resolvePromise(line);
32
+ };
33
+ reader.on('line', handler);
34
+ });
35
+ }
36
+
37
+ async function processNext(): Promise<void> {
38
+ if (processing) return;
39
+ processing = true;
40
+ try {
41
+ while (queue.length > 0) {
42
+ const req = queue.shift()!;
43
+ // If the request was already resolved by another path while queued, skip it.
44
+ if (!gateway.pending().some((p) => p.id === req.id)) continue;
45
+
46
+ const optionsStr = req.options.join(' / ');
47
+ process.stdout.write(
48
+ `\n[APPROVAL REQUIRED] ${req.message}\n` +
49
+ ` id: ${req.id}\n` +
50
+ ` task: ${req.taskId}${req.trackId ? ` (track: ${req.trackId})` : ''}\n` +
51
+ ` options: ${optionsStr}\n` +
52
+ ` > `,
53
+ );
54
+
55
+ const input = (await readOneLine()).trim().toLowerCase();
56
+
57
+ const approveAliases = new Set(['approve', 'yes', 'y', 'ok', 'true', '1']);
58
+ const rejectAliases = new Set(['reject', 'no', 'n', 'deny', 'false', '0']);
59
+ const matchedOption = req.options.find((o) => o.toLowerCase() === input);
60
+
61
+ if (matchedOption) {
62
+ const isReject = rejectAliases.has(matchedOption.toLowerCase());
63
+ gateway.resolve(req.id, {
64
+ outcome: isReject ? 'rejected' : 'approved',
65
+ choice: matchedOption,
66
+ actor: 'cli',
67
+ });
68
+ } else if (approveAliases.has(input)) {
69
+ gateway.resolve(req.id, { outcome: 'approved', choice: input, actor: 'cli' });
70
+ } else if (rejectAliases.has(input)) {
71
+ gateway.resolve(req.id, {
72
+ outcome: 'rejected',
73
+ choice: input,
74
+ actor: 'cli',
75
+ reason: 'user rejected via CLI',
76
+ });
77
+ } else {
78
+ process.stdout.write(` unrecognized input "${input}" — treating as rejection\n`);
79
+ gateway.resolve(req.id, {
80
+ outcome: 'rejected',
81
+ actor: 'cli',
82
+ reason: `unrecognized CLI input: ${input}`,
83
+ });
84
+ }
85
+ }
86
+ } finally {
87
+ processing = false;
88
+ }
89
+ }
90
+
91
+ const unsubscribe = gateway.subscribe((event) => {
92
+ switch (event.type) {
93
+ case 'requested':
94
+ queue.push(event.request);
95
+ void processNext();
96
+ return;
97
+ case 'resolved':
98
+ case 'expired':
99
+ case 'aborted': {
100
+ // Drop from queue if it's still waiting its turn.
101
+ const idx = queue.findIndex((r) => r.id === event.request.id);
102
+ if (idx >= 0) queue.splice(idx, 1);
103
+ return;
104
+ }
105
+ }
106
+ });
107
+
108
+ return {
109
+ detach: () => {
110
+ unsubscribe();
111
+ if (rl) {
112
+ rl.close();
113
+ rl = null;
114
+ }
115
+ },
116
+ };
117
+ }