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 +26 -3
- package/package.json +2 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/commands/repl.js +1 -1
- package/packages/cli/src/executor.js +12 -6
- package/packages/cli/src/index.js +1 -1
- package/packages/cli/src/runners/claude.js +6 -3
- package/packages/cli/src/runners/codex.js +6 -3
- package/packages/cli/src/runners/copilot.js +24 -6
- package/packages/cli/src/runners/opencode.js +1 -1
- package/packages/server/package.json +1 -1
- package/packages/web/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
# Singleton Pipeline Builder (v0.4.0-beta.
|
|
1
|
+
# Singleton Pipeline Builder (v0.4.0-beta.11)
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/singleton-pipeline)
|
|
4
|
+
[](https://www.npmjs.com/package/singleton-pipeline)
|
|
5
|
+
[](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.
|
|
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",
|
|
@@ -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.
|
|
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
|
-
|
|
194
|
-
|
|
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
|
|
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
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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.
|
|
101
|
-
|
|
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
|
|
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.
|
|
132
|
-
|
|
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
|
|
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({
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
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: ['
|
|
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) => {
|