@tagma/sdk 0.3.8 → 0.4.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.
package/README.md CHANGED
@@ -1,11 +1,25 @@
1
1
  # @tagma/sdk
2
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.
3
+ > ## ⚠️ Bun-only package do **not** `npm install`
4
+ >
5
+ > ```bash
6
+ > bun add @tagma/sdk
7
+ > ```
8
+ >
9
+ > This package ships TypeScript source (`main: ./src/sdk.ts`) and relies on
10
+ > Bun's native `.ts` loader. `npm` / `yarn` / `pnpm` installs are blocked by a
11
+ > `preinstall` guard and will fail by design. Get Bun at <https://bun.sh>.
12
+ >
13
+ > *(The `npm i @tagma/sdk` line in the npmjs.com sidebar is auto-generated by
14
+ > the npm registry website and cannot be removed — please ignore it and use
15
+ > the command above.)*
4
16
 
5
- > **Bun 1.3 required.** `node` + `npm` are not supported this package ships TypeScript source (`main: ./src/sdk.ts`) and relies on Bun's native `.ts` loader. Install with `bun add`, not `npm install`.
17
+ 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.
6
18
 
7
19
  ## Install
8
20
 
21
+ **Requires Bun ≥ 1.3.** Node/npm are not supported.
22
+
9
23
  ```bash
10
24
  bun add @tagma/sdk
11
25
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagma/sdk",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,13 +15,15 @@
15
15
  ".": "./src/sdk.ts"
16
16
  },
17
17
  "files": [
18
- "src"
18
+ "src",
19
+ "scripts/preinstall.js"
19
20
  ],
20
21
  "engines": {
21
22
  "bun": ">=1.3"
22
23
  },
23
24
  "packageManager": "bun@1.3.7",
24
25
  "scripts": {
26
+ "preinstall": "node scripts/preinstall.js",
25
27
  "test": "bun test",
26
28
  "release": "bun scripts/release.ts",
27
29
  "release:publish": "bun scripts/release.ts --publish"
@@ -0,0 +1,38 @@
1
+ // Preinstall guard — refuses installation under npm / yarn / pnpm.
2
+ // @tagma/sdk ships TypeScript source (main: ./src/sdk.ts) and relies on
3
+ // Bun's native .ts loader, so installing under Node-based managers leaves
4
+ // users with a broken package.
5
+ //
6
+ // Bun detection: we can't rely on process.versions.bun alone, because bun
7
+ // invokes lifecycle scripts via the interpreter named in the script command
8
+ // ("node scripts/preinstall.js" runs under node even when called from bun
9
+ // install). Bun does, however, set npm_config_user_agent to a string that
10
+ // starts with "bun/<version> ...", which is the canonical cross-manager
11
+ // signal. Check both for safety.
12
+
13
+ const ua = process.env.npm_config_user_agent || '';
14
+ if (process.versions.bun || ua.startsWith('bun/') || ua.startsWith('bun ')) {
15
+ process.exit(0);
16
+ }
17
+
18
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
19
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
20
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
21
+
22
+ process.stderr.write(
23
+ [
24
+ '',
25
+ red(bold(' @tagma/sdk requires Bun (>= 1.3).')),
26
+ '',
27
+ ' This package ships TypeScript source and uses Bun\'s native .ts loader.',
28
+ ' npm / yarn / pnpm cannot consume it.',
29
+ '',
30
+ ' Install with:',
31
+ cyan(' bun add @tagma/sdk'),
32
+ '',
33
+ ' Get Bun: https://bun.sh',
34
+ '',
35
+ ].join('\n') + '\n',
36
+ );
37
+
38
+ process.exit(1);
package/src/schema.ts CHANGED
@@ -41,14 +41,16 @@ function validateRawTask(task: RawTaskConfig, trackId: string): void {
41
41
  if (!task.id) throw new Error(`track "${trackId}": task.id is required`);
42
42
  if (task.use) return; // template usage, validated later
43
43
 
44
- const hasPrompt = typeof task.prompt === 'string' && task.prompt.length > 0;
45
- const hasCommand = typeof task.command === 'string' && task.command.length > 0;
46
- if (!hasPrompt && !hasCommand) {
44
+ const hasPromptKey = typeof task.prompt === 'string';
45
+ const hasCommandKey = typeof task.command === 'string';
46
+ if (!hasPromptKey && !hasCommandKey) {
47
47
  throw new Error(`task "${task.id}": must have either "prompt" or "command"`);
48
48
  }
49
- if (hasPrompt && hasCommand) {
49
+ if (hasPromptKey && hasCommandKey) {
50
50
  throw new Error(`task "${task.id}": cannot have both "prompt" and "command"`);
51
51
  }
52
+ // Empty-content tasks (e.g. `prompt: ''`) are allowed at parse time and
53
+ // flagged as non-fatal validation errors by validate-raw.ts.
52
54
  }
53
55
 
54
56
  // ═══ Template Expansion ═══
@@ -111,19 +111,30 @@ export function validateRaw(config: RawPipelineConfig): ValidationError[] {
111
111
  // Template-based tasks: skip prompt/command checks (params validated at runtime)
112
112
  if (task.use) continue;
113
113
 
114
- const hasPrompt = typeof task.prompt === 'string' && task.prompt.trim().length > 0;
115
- const hasCommand = typeof task.command === 'string' && task.command.trim().length > 0;
114
+ const hasPromptKey = typeof task.prompt === 'string';
115
+ const hasCommandKey = typeof task.command === 'string';
116
+ const promptEmpty = hasPromptKey && task.prompt!.trim().length === 0;
117
+ const commandEmpty = hasCommandKey && task.command!.trim().length === 0;
116
118
 
117
- if (!hasPrompt && !hasCommand) {
119
+ if (hasPromptKey && hasCommandKey) {
120
+ errors.push({
121
+ path: taskPath,
122
+ message: `Task "${task.id}": cannot have both "prompt" and "command"`,
123
+ });
124
+ } else if (!hasPromptKey && !hasCommandKey) {
118
125
  errors.push({
119
126
  path: taskPath,
120
127
  message: `Task "${task.id}": must have "prompt" or "command"`,
121
128
  });
122
- }
123
- if (hasPrompt && hasCommand) {
129
+ } else if (promptEmpty) {
124
130
  errors.push({
125
131
  path: taskPath,
126
- message: `Task "${task.id}": cannot have both "prompt" and "command"`,
132
+ message: `Task "${task.id}": prompt content cannot be empty`,
133
+ });
134
+ } else if (commandEmpty) {
135
+ errors.push({
136
+ path: taskPath,
137
+ message: `Task "${task.id}": command content cannot be empty`,
127
138
  });
128
139
  }
129
140