singleton-pipeline 0.4.0-beta.0 → 0.4.0-beta.11

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,4 +1,8 @@
1
- # Singleton Pipeline Builder (v0.4.0-beta.0)
1
+ # Singleton Pipeline Builder (v0.4.0-beta.11)
2
+
3
+ [![npm version](https://img.shields.io/npm/v/singleton-pipeline/beta.svg)](https://www.npmjs.com/package/singleton-pipeline)
4
+ [![npm downloads](https://img.shields.io/npm/dm/singleton-pipeline.svg)](https://www.npmjs.com/package/singleton-pipeline)
5
+ [![license](https://img.shields.io/npm/l/singleton-pipeline.svg)](LICENSE)
2
6
 
3
7
  Build multi-agent pipelines for your codebase, visually.
4
8
 
@@ -57,13 +61,32 @@ This beta focused on multi-provider execution, Copilot support, inspection, and
57
61
 
58
62
  ## Install
59
63
 
64
+ The fastest path is via npm:
65
+
60
66
  ```bash
67
+ npm install -g singleton-pipeline@beta
68
+ singleton --version
69
+ ```
70
+
71
+ Or run it directly without installing globally:
72
+
73
+ ```bash
74
+ npx singleton-pipeline@beta --help
75
+ ```
76
+
77
+ Singleton drives the provider CLIs you already use; install the ones you want in your `$PATH` with a working session: `claude`, `codex`, `copilot`, `opencode`.
78
+
79
+ Requirements: Node 20+.
80
+
81
+ ### From source (development)
82
+
83
+ ```bash
84
+ git clone https://github.com/RomainLENTZ/singleton_pipeline.git
85
+ cd singleton_pipeline
61
86
  npm install
62
87
  npm link # optional, to use `singleton` globally
63
88
  ```
64
89
 
65
- Requirements: Node 20+, plus the provider CLIs you want to use in your `$PATH` with a working session: `claude`, `codex`, `copilot`, and/or `opencode`.
66
-
67
90
  ## Quickstart
68
91
 
69
92
  Run the bundled mixed-provider example end-to-end (uses Claude for scouting/review and Codex for implementation):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "singleton-pipeline",
3
- "version": "0.4.0-beta.0",
3
+ "version": "0.4.0-beta.11",
4
4
  "description": "Visual pipeline builder for multi-agent AI workflows. Orchestrates Claude Code, Codex, Copilot, and OpenCode under a unified security policy.",
5
5
  "keywords": [
6
6
  "ai",
@@ -53,6 +53,7 @@
53
53
  "chalk": "^5.6.2",
54
54
  "commander": "^12.1.0",
55
55
  "cors": "^2.8.5",
56
+ "cross-spawn": "^7.0.6",
56
57
  "express": "^4.21.2",
57
58
  "fast-glob": "^3.3.2",
58
59
  "figlet": "^1.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singleton/cli",
3
- "version": "0.4.0-beta.0",
3
+ "version": "0.4.0-beta.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "singleton": "./src/index.js"
@@ -202,7 +202,7 @@ const SINGLETON_RAW = [
202
202
  ];
203
203
 
204
204
  const ART_WIDTH = Math.max(...SINGLETON_RAW.map((l) => l.length));
205
- const APP_VERSION = 'v0.4.0-beta.0';
205
+ const APP_VERSION = 'v0.4.0-beta.11';
206
206
 
207
207
  async function showWelcome(root, shell) {
208
208
  const now = new Date();
@@ -190,14 +190,19 @@ function buildUserMessage(resolvedInputs, outputNames, workspaceInfo, securityPo
190
190
  parts.push(...securityBlock);
191
191
  parts.push('');
192
192
  }
193
- for (const [name, value] of Object.entries(resolvedInputs)) {
194
- parts.push(`<${name}>\n${value}\n</${name}>`);
193
+ const inputEntries = Object.entries(resolvedInputs);
194
+ if (inputEntries.length) {
195
+ parts.push('The user provides the following inputs. These are concrete values to use literally — they are NOT placeholders, examples, or templates. Do not invent or substitute different values; do not skip the task because they look like markup.');
196
+ parts.push('');
197
+ for (const [name, value] of inputEntries) {
198
+ parts.push(`<${name}>\n${value}\n</${name}>`);
199
+ }
200
+ parts.push('');
195
201
  }
196
- parts.push('');
197
202
  if (outputNames.length === 1) {
198
- parts.push(`Provide your response as the <${outputNames[0]}> content directly (no XML wrapper needed).`);
203
+ parts.push(`Follow your agent instructions to process these inputs. Provide your response as the <${outputNames[0]}> content directly (no XML wrapper needed).`);
199
204
  } else {
200
- parts.push('Provide your response with each output wrapped in its own XML block:');
205
+ parts.push('Follow your agent instructions to process these inputs. Provide your response with each output wrapped in its own XML block:');
201
206
  for (const name of outputNames) parts.push(`<${name}>...</${name}>`);
202
207
  }
203
208
  return parts.join('\n');
@@ -1076,7 +1081,8 @@ function shouldHighlightSecurity({ provider, permissionMode, securityPolicy }) {
1076
1081
 
1077
1082
  function commandExists(command) {
1078
1083
  return new Promise((resolve) => {
1079
- const child = spawn('which', [command], { stdio: 'ignore' });
1084
+ const lookup = process.platform === 'win32' ? 'where' : 'which';
1085
+ const child = spawn(lookup, [command], { stdio: 'ignore' });
1080
1086
  child.on('error', () => resolve(false));
1081
1087
  child.on('close', (code) => resolve(code === 0));
1082
1088
  });
@@ -22,7 +22,7 @@ function groupAgentsByProvider(agents) {
22
22
  program
23
23
  .name('singleton')
24
24
  .description('Singleton Pipeline Builder — scan agents and build pipelines')
25
- .version('0.4.0-beta.0');
25
+ .version('0.4.0-beta.11');
26
26
 
27
27
  program
28
28
  .command('scan')
@@ -1,4 +1,4 @@
1
- import { spawn } from 'node:child_process';
1
+ import spawn from 'cross-spawn';
2
2
 
3
3
  const DEFAULT_TIMEOUT_MS = Number(process.env.SINGLETON_RUNNER_TIMEOUT_MS) || 10 * 60 * 1000;
4
4
  const ALLOWED_PERMISSION_MODES = new Set(['bypassPermissions']);
@@ -97,8 +97,11 @@ export const claudeRunner = {
97
97
  }
98
98
  });
99
99
 
100
- child.stdin.write(userPrompt);
101
- child.stdin.end();
100
+ child.stdin.on('error', () => { /* surfaced via close handler */ });
101
+ try {
102
+ child.stdin.write(userPrompt);
103
+ child.stdin.end();
104
+ } catch { /* same */ }
102
105
  });
103
106
 
104
107
  return {
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { spawn } from 'node:child_process';
4
+ import spawn from 'cross-spawn';
5
5
  import { discoverCodexProjectInstructions } from './codex-instructions.js';
6
6
  import { findUsage, safeJsonParse } from './_shared.js';
7
7
 
@@ -128,8 +128,11 @@ export const codexRunner = {
128
128
  resolve({ events, stderr: stderrText });
129
129
  });
130
130
 
131
- child.stdin.write(prompt);
132
- child.stdin.end();
131
+ child.stdin.on('error', () => { /* surfaced via close handler */ });
132
+ try {
133
+ child.stdin.write(prompt);
134
+ child.stdin.end();
135
+ } catch { /* same */ }
133
136
  });
134
137
 
135
138
  let text = '';
@@ -1,4 +1,4 @@
1
- import { spawn } from 'node:child_process';
1
+ import spawn from 'cross-spawn';
2
2
  import path from 'node:path';
3
3
  import { extractText, safeJsonParse } from './_shared.js';
4
4
 
@@ -56,10 +56,15 @@ export function buildCopilotPermissionArgs(securityPolicy = {}) {
56
56
  args.push('--allow-tool=write');
57
57
  }
58
58
 
59
+ // Copilot CLI runs in deny-by-default mode as soon as any --allow-tool is
60
+ // present. Agents need shell access to list/grep the codebase even when their
61
+ // write surface is restricted — otherwise the scout can't discover anything.
62
+ // read-only stays shell-less; dangerous is already covered by --allow-all-tools.
59
63
  if (profile === 'read-only') {
60
64
  args.push('--deny-tool=write');
61
65
  args.push('--deny-tool=shell');
62
66
  } else {
67
+ if (profile !== 'dangerous') args.push('--allow-tool=shell');
63
68
  args.push('--deny-tool=shell(git push)');
64
69
  }
65
70
 
@@ -76,10 +81,13 @@ export function buildCopilotPermissionArgs(securityPolicy = {}) {
76
81
  return args;
77
82
  }
78
83
 
79
- export function buildCopilotArgs({ prompt, model, runnerAgent, securityPolicy = {} } = {}) {
84
+ export function buildCopilotArgs({ model, runnerAgent, securityPolicy = {} } = {}) {
85
+ // Prompt is written to stdin (see copilotRunner.run). We use `-p -` as the
86
+ // marker for "read prompt from stdin". This avoids the Windows command-line
87
+ // length limit (~32KB) when the user message includes large injected context.
80
88
  const args = [
81
89
  '-p',
82
- prompt,
90
+ '-',
83
91
  '--output-format',
84
92
  'json',
85
93
  ...buildCopilotPermissionArgs(securityPolicy),
@@ -137,11 +145,15 @@ export const copilotRunner = {
137
145
  securityPolicy,
138
146
  timeoutMs = DEFAULT_TIMEOUT_MS,
139
147
  }) {
140
- const prompt = buildPrompt(systemPrompt, userPrompt);
141
- const args = buildCopilotArgs({ prompt, model, runnerAgent, securityPolicy });
148
+ // When --agent is used, Copilot loads the system prompt from .github/agents/<name>.md.
149
+ // We pipe only the user prompt via stdin in that case (to match the bash
150
+ // pattern). When --agent is not used we inline the system prompt with the
151
+ // XML wrappers.
152
+ const prompt = runnerAgent ? userPrompt : buildPrompt(systemPrompt, userPrompt);
153
+ const args = buildCopilotArgs({ model, runnerAgent, securityPolicy });
142
154
 
143
155
  const { events, stderr } = await new Promise((resolve, reject) => {
144
- const child = spawn('copilot', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
156
+ const child = spawn('copilot', args, { cwd, stdio: ['pipe', 'pipe', 'pipe'] });
145
157
  const stdoutChunks = [];
146
158
  let stderrText = '';
147
159
  let timedOut = false;
@@ -152,6 +164,12 @@ export const copilotRunner = {
152
164
  setTimeout(() => child.kill('SIGKILL'), 5000).unref();
153
165
  }, timeoutMs);
154
166
 
167
+ child.stdin.on('error', () => { /* surfaced via close handler */ });
168
+ try {
169
+ child.stdin.write(prompt);
170
+ child.stdin.end();
171
+ } catch { /* same */ }
172
+
155
173
  child.stdout.on('data', (d) => stdoutChunks.push(d.toString()));
156
174
  child.stderr.on('data', (d) => (stderrText += d.toString()));
157
175
  child.on('error', (err) => {
@@ -1,4 +1,4 @@
1
- import { spawn } from 'node:child_process';
1
+ import spawn from 'cross-spawn';
2
2
  import path from 'node:path';
3
3
  import { extractText, findCostUsd, findUsage, safeJsonParse } from './_shared.js';
4
4
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singleton/server",
3
- "version": "0.4.0-beta.0",
3
+ "version": "0.4.0-beta.11",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "dependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singleton/web",
3
- "version": "0.4.0-beta.0",
3
+ "version": "0.4.0-beta.11",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {