robisie 0.0.0-stage → 0.1.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/CHANGELOG.md +18 -0
- package/README.md +27 -2
- package/dist/agents.js +185 -0
- package/dist/cli.js +137 -0
- package/dist/commands.js +318 -0
- package/dist/constants.js +34 -0
- package/dist/context.js +65 -0
- package/dist/fsops.js +438 -0
- package/dist/helpers.js +95 -0
- package/dist/manifest.js +156 -0
- package/dist/mcp.js +127 -0
- package/dist/roles.js +119 -0
- package/docs/README.en.md +174 -0
- package/docs/README.md +174 -0
- package/docs/RELEASE-0.1.0.md +29 -0
- package/helpers/ceo-client-launcher-mcp.mjs +579 -0
- package/helpers/client-launch-context-mcp.mjs +538 -0
- package/helpers/lib/client-review-receipt.mjs +705 -0
- package/helpers/lib/client-spawn-failure.mjs +129 -0
- package/package.json +30 -4
- package/roles/ceo-client.md +192 -0
- package/roles/manager-builder-client.md +106 -0
- package/roles/quality-officer-client.md +191 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Historia zmian klienta / Client changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
Kandydat pierwszego wydania, bez daty publikacji. / First release candidate, no publication date.
|
|
6
|
+
|
|
7
|
+
- `init`, `doctor`, `uninstall` dla Claude Code, Codex CLI i OpenCode; role CEO, Builder i Quality Officer.
|
|
8
|
+
- Pomocniki `client-launch-context` i `ceo-client-launcher` dostarczane w paczce, polecenia przypięte do wersji klienta.
|
|
9
|
+
- Kontrakt serwera 1.x (obecnie 1.0.0); odmowa innego major przed zapisem konfiguracji.
|
|
10
|
+
- Ochrona obcych ustawień i instrukcji, pierwsze kopie zapasowe oraz przywracanie własnych plików po uninstall; zmienione role pozostają z ostrzeżeniem.
|
|
11
|
+
- Zapisywanie dostawcy dla każdej roli i diagnoza ograniczeń; bieżąca pętla wymaga jednego dostawcy dla wszystkich trzech ról.
|
|
12
|
+
- Notatka PL/EN i lokalna próba reinstalacji 0.1.0 → syntetyczne 0.1.1 → 0.1.0; to nie rollback opublikowanych wydań.
|
|
13
|
+
|
|
14
|
+
Pochodzenie klienta: [PR #209](https://github.com/chuppa12/robisie-app/pull/209),
|
|
15
|
+
[merge a41b04ee7961f16e74dbf3a88b5a8199c5339f41](https://github.com/chuppa12/robisie-app/commit/a41b04ee7961f16e74dbf3a88b5a8199c5339f41),
|
|
16
|
+
[ochrona kopii 5ec7cde32a926c593f46e5fb2c86c79fe31bf332](https://github.com/chuppa12/robisie-app/commit/5ec7cde32a926c593f46e5fb2c86c79fe31bf332),
|
|
17
|
+
[bezpieczny zapis b15d73ff2f9f1f1b26de7041ec0623d69d37d347](https://github.com/chuppa12/robisie-app/commit/b15d73ff2f9f1f1b26de7041ec0623d69d37d347),
|
|
18
|
+
[pomocniki i uninstall 36e7e39ed22339a29f851f08c9307d452554981f](https://github.com/chuppa12/robisie-app/commit/36e7e39ed22339a29f851f08c9307d452554981f).
|
package/README.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# robisie 0.1.0
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Przewodnik dla otrzymanego `robisie-0.1.0.tgz`: [PL — konto → odbiór → odinstalowanie](docs/README.md) · [EN — account → acceptance → uninstall](docs/README.en.md). Zacznij od wymagań, konta i klucza w przewodniku. Przygotuj Node.js 20+, npm, program jednego dostawcy AI oraz pusty katalog z tgz i README; strona i package.json nie są potrzebne. Projekt w panelu i katalog na dysku to osobne rzeczy; CEO wymaga dokładnie jednego projektu z discovery.
|
|
4
|
+
|
|
5
|
+
Człowiek generuje klucz worker z etykietą w Connect i podaje go poza rozmową do ukrytego pytania init. W zsh/bash usuń stary ENV, a po udanym init wczytaj zapisany klucz do bieżącej powłoki bez wyświetlania go — init nie ustawia jej środowiska. Dla pierwszej próby Claude:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm i ./robisie-0.1.0.tgz
|
|
9
|
+
unset ROBISIE_KEY
|
|
10
|
+
npx robisie init --agent claude --role ceo=claude --role builder=claude --role quality=claude
|
|
11
|
+
[ -r "${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key" ] && export ROBISIE_KEY="$(cat "${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key")"
|
|
12
|
+
npx robisie doctor
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Dla Codex/OpenCode zastąp wszystkie cztery nazwy dostawcy. Sam --agent nie usuwa wcześniejszej mapy. Nowy terminal wymaga obsługi profilu zsh/bash bez --no-shell; w innych sesjach wczytaj środowisko zgodnie z przewodnikiem. Agenta uruchom z katalogu instalacji i sprawdź narzędzia obu pomocników: doctor startuje je przez node, agent przez npx; ROBISIE_PROJECT_CONTEXT nie zmienia cwd szukania paczki.
|
|
16
|
+
|
|
17
|
+
Człowiek startuje CEO i ręcznie Buildera. Niezależną recenzję uruchamia CEO przez Agent; bez tego narzędzia w Codex/OpenCode praca pozostaje in_review i wymaga naprawy integracji przez CEO/CR. Worker nie ustawia done; człowiek odbiera wynik w panelu po dowodach i recenzji. Pełne polecenia, wynik pierwszej próby, naprawy, aktualizacja z tgz i uninstall są w PL/EN powyżej.
|
|
18
|
+
|
|
19
|
+
Wybór i zmiana dostawcy ról:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npx robisie init --role ceo=claude --role builder=codex --role quality=opencode
|
|
23
|
+
npx robisie init --role=builder=claude
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Dostawcy: `claude`, `codex`, `opencode`; role: `ceo`, `builder`, `quality`. Mapa pozostaje po ponownym init; zmiana jednej roli zachowuje pozostałe. Każdy wskazany dostawca oraz `--agent` otrzymuje komplet ról, MCP i pomocników; poprzedni pozostaje do pełnego uninstall. Bez `--agent` init uwzględnia także wszystkich wykrytych dostawców, również nowo zainstalowanych przy ponownym uruchomieniu. Stary manifest z jednym dostawcą przypisuje mu trzy role; z wieloma zapisuje null. Init bez wyboru nadal działa; jawny wybór wymaga pełnej wynikowej mapy. Wadliwa mapa blokuje init, ale nie uninstall po entries.
|
|
27
|
+
|
|
28
|
+
Doctor pokazuje mapę i sprawdza programy w PATH. Mieszane role oznaczają kod 1: Quality Officer działa jako podagent u dostawcy CEO, a polecenie Buildera ma format dostawcy CEO. Wykonanie mieszanych ról to następny zakres. Szczegóły i diagnoza są w przewodnikach PL/EN powyżej.
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { delimiter, join } from "node:path";
|
|
3
|
+
import { HELPER_ENV, HELPER_SERVERS, KEY_ENV, MCP_SERVER_NAME, helperCommand, mcpUrl } from "./constants.js";
|
|
4
|
+
import { RobisieError } from "./context.js";
|
|
5
|
+
import { renderClaudeAgent, renderCodexRole, renderOpenCodeAgent } from "./roles.js";
|
|
6
|
+
export const AGENT_IDS = ["claude", "codex", "opencode"];
|
|
7
|
+
export const AGENT_LABELS = { claude: "Claude Code", codex: "Codex CLI", opencode: "OpenCode" };
|
|
8
|
+
const BINARIES = { claude: "claude", codex: "codex", opencode: "opencode" };
|
|
9
|
+
export function isAgentId(value) {
|
|
10
|
+
return AGENT_IDS.includes(value);
|
|
11
|
+
}
|
|
12
|
+
/** Where each agent keeps its user-level configuration (the same places the agents themselves read). */
|
|
13
|
+
export function agentPaths(ctx) {
|
|
14
|
+
const codexHome = ctx.env.CODEX_HOME || join(ctx.home, ".codex");
|
|
15
|
+
const configHome = ctx.env.XDG_CONFIG_HOME || join(ctx.home, ".config");
|
|
16
|
+
const opencodeDir = join(configHome, "opencode");
|
|
17
|
+
return {
|
|
18
|
+
// The package's own directory. Never ~/.robisie: on the owner's machine that belongs to the factory.
|
|
19
|
+
clientDir: join(configHome, "robisie"),
|
|
20
|
+
claudeDir: join(ctx.home, ".claude"),
|
|
21
|
+
claudeJson: join(ctx.home, ".claude.json"),
|
|
22
|
+
codexHome,
|
|
23
|
+
codexConfig: join(codexHome, "config.toml"),
|
|
24
|
+
codexAgentsMd: join(codexHome, "AGENTS.md"),
|
|
25
|
+
opencodeDir,
|
|
26
|
+
opencodeJson: join(opencodeDir, "opencode.json"),
|
|
27
|
+
opencodeJsonc: join(opencodeDir, "opencode.jsonc"),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function agentOnPath(ctx, id) {
|
|
31
|
+
const binary = BINARIES[id];
|
|
32
|
+
const names = ctx.platform === "win32" ? [`${binary}.exe`, `${binary}.cmd`] : [binary];
|
|
33
|
+
for (const dir of (ctx.env.PATH ?? "").split(delimiter)) {
|
|
34
|
+
if (!dir)
|
|
35
|
+
continue;
|
|
36
|
+
for (const name of names) {
|
|
37
|
+
try {
|
|
38
|
+
const path = join(dir, name);
|
|
39
|
+
if (!statSync(path).isFile())
|
|
40
|
+
continue;
|
|
41
|
+
accessSync(path, ctx.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// not here — keep looking
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
/** Detection reads only the filesystem (config dir present, or binary on PATH) — nothing is executed. */
|
|
52
|
+
export function detectAgents(ctx) {
|
|
53
|
+
const paths = agentPaths(ctx);
|
|
54
|
+
const dirs = { claude: paths.claudeDir, codex: paths.codexHome, opencode: paths.opencodeDir };
|
|
55
|
+
return AGENT_IDS.filter((id) => existsSync(dirs[id]) || agentOnPath(ctx, id));
|
|
56
|
+
}
|
|
57
|
+
/** Every server name init writes into `[mcp_servers]`. */
|
|
58
|
+
const CODEX_SERVERS = [MCP_SERVER_NAME, ...HELPER_SERVERS].map((name) => name.replace(/[-]/g, "\\-")).join("|");
|
|
59
|
+
const CODEX_SERVER_KEY = `(?:"(?:${CODEX_SERVERS})"|'(?:${CODEX_SERVERS})'|(?:${CODEX_SERVERS}))`;
|
|
60
|
+
const TOML_ROBISIE_HEADER = new RegExp(`^\\s*\\[\\s*mcp_servers\\s*\\.\\s*${CODEX_SERVER_KEY}\\s*(?:\\.[^\\]]*)?\\]\\s*(?:#.*)?$`, "m");
|
|
61
|
+
const TOML_ANY_HEADER = /^\s*\[/;
|
|
62
|
+
const INLINE_TOP = new RegExp(`^\\s*mcp_servers\\s*\\.\\s*${CODEX_SERVER_KEY}\\s*[.=]`);
|
|
63
|
+
const INLINE_IN_TABLE = new RegExp(`^\\s*${CODEX_SERVER_KEY}\\s*[.=]`);
|
|
64
|
+
/** Keep a multiline string or array in its assignment, so its contents cannot look like server headers. */
|
|
65
|
+
function tomlStatements(text) {
|
|
66
|
+
const lines = [];
|
|
67
|
+
let start = 0;
|
|
68
|
+
let depth = 0;
|
|
69
|
+
for (const token of text.matchAll(/"""(?:\\[\s\S]|(?!""")[^\\])*""""{0,2}|'''[\s\S]*?''''{0,2}|"(?:\\[^\r\n]|[^"\\\r\n])*"|'[^'\r\n]*'|#[^\r\n]*|[\s\S]/g)) {
|
|
70
|
+
const value = token[0];
|
|
71
|
+
if (value === "[" || value === "{")
|
|
72
|
+
depth += 1;
|
|
73
|
+
if (value === "]" || value === "}")
|
|
74
|
+
depth -= 1;
|
|
75
|
+
if (value === "\n" && depth === 0) {
|
|
76
|
+
lines.push(text.slice(start, token.index + 1));
|
|
77
|
+
start = token.index + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (start < text.length)
|
|
81
|
+
lines.push(text.slice(start));
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Take over an `[mcp_servers.robisie]` table written earlier by hand or by `codex mcp add` (otherwise
|
|
86
|
+
* Codex would see the table twice and refuse the whole config). Backups retain the old configuration.
|
|
87
|
+
*/
|
|
88
|
+
export function removeCodexTables(text, removeInline = false) {
|
|
89
|
+
const lines = tomlStatements(text);
|
|
90
|
+
const kept = [];
|
|
91
|
+
let inRobisie = false;
|
|
92
|
+
let table = "";
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const header = /^\s*\[\s*([^\]]*?)\s*\]/.exec(line);
|
|
95
|
+
if (header)
|
|
96
|
+
table = (header[1] ?? "").replace(/\s+/g, "");
|
|
97
|
+
if (TOML_ANY_HEADER.test(line))
|
|
98
|
+
inRobisie = TOML_ROBISIE_HEADER.test(line);
|
|
99
|
+
if (inRobisie)
|
|
100
|
+
continue;
|
|
101
|
+
const inline = (table === "" && INLINE_TOP.test(line)) || (table === "mcp_servers" && INLINE_IN_TABLE.test(line));
|
|
102
|
+
if (!inline)
|
|
103
|
+
kept.push(line);
|
|
104
|
+
else if (!removeInline) {
|
|
105
|
+
throw new RobisieError("W ~/.codex/config.toml jest już serwer robisie zapisany w formie, której nie umiem bezpiecznie przejąć.", "Uruchom `codex mcp remove robisie`, potem ponownie `npx robisie init`.");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return kept.join("");
|
|
109
|
+
}
|
|
110
|
+
function codexHelperTable(name) {
|
|
111
|
+
const { command, args } = helperCommand(name);
|
|
112
|
+
return [
|
|
113
|
+
`[mcp_servers.${name}]`,
|
|
114
|
+
`command = "${command}"`,
|
|
115
|
+
`args = [${args.map((arg) => JSON.stringify(arg)).join(", ")}]`,
|
|
116
|
+
// Codex passes only a small default environment to stdio servers; the helpers need these three.
|
|
117
|
+
`env_vars = [${HELPER_ENV.map((name) => JSON.stringify(name)).join(", ")}]`,
|
|
118
|
+
"startup_timeout_sec = 60",
|
|
119
|
+
].join("\n");
|
|
120
|
+
}
|
|
121
|
+
/** Every file change `init` makes for one agent. */
|
|
122
|
+
export function planAgent(ctx, id, roles, key) {
|
|
123
|
+
const paths = agentPaths(ctx);
|
|
124
|
+
const url = mcpUrl(ctx.origin);
|
|
125
|
+
if (id === "claude") {
|
|
126
|
+
return [
|
|
127
|
+
// User scope (~/.claude.json, the file `claude mcp add -s user` writes): private, never committed.
|
|
128
|
+
{ kind: "json", path: paths.claudeJson, keyPath: ["mcpServers", MCP_SERVER_NAME], value: { type: "http", url, headers: { Authorization: `Bearer ${key}` } }, private: true },
|
|
129
|
+
...roles.map((role) => ({ kind: "file", path: join(paths.claudeDir, "agents", role.file), content: renderClaudeAgent(role) })),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
if (id === "codex") {
|
|
133
|
+
const roleFiles = roles.map((role) => ({ role, path: join(paths.clientDir, "roles", "codex", role.file) }));
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
kind: "block",
|
|
137
|
+
path: paths.codexConfig,
|
|
138
|
+
comment: "hash",
|
|
139
|
+
body: [`[mcp_servers.${MCP_SERVER_NAME}]\nurl = "${url}"\nbearer_token_env_var = "${KEY_ENV}"`, ...HELPER_SERVERS.map(codexHelperTable)].join("\n\n"),
|
|
140
|
+
clean: removeCodexTables,
|
|
141
|
+
},
|
|
142
|
+
...roleFiles.map(({ role, path }) => ({ kind: "file", path, content: renderCodexRole(role, roleFiles.find((r) => r.role.kind === "builder")?.path) })),
|
|
143
|
+
{
|
|
144
|
+
kind: "block",
|
|
145
|
+
path: paths.codexAgentsMd,
|
|
146
|
+
comment: "html",
|
|
147
|
+
body: [
|
|
148
|
+
"## robisie — client roles",
|
|
149
|
+
"",
|
|
150
|
+
"robisie (https://robisie.app) installed three client roles. When the operator asks you to act as one of them, read that file in full and follow it as your operating contract for the whole session:",
|
|
151
|
+
"",
|
|
152
|
+
...roleFiles.map(({ role, path }) => `- ${role.label}: \`${path}\``),
|
|
153
|
+
"",
|
|
154
|
+
"One role per session. Board tools come from the MCP server `robisie`; the credential is the `ROBISIE_KEY` environment variable.",
|
|
155
|
+
].join("\n"),
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
if (existsSync(paths.opencodeJsonc) && !existsSync(paths.opencodeJson)) {
|
|
160
|
+
throw new RobisieError(`OpenCode używa pliku ${paths.opencodeJsonc} (z komentarzami), którego nie zmieniam automatycznie.`, `Dodaj w nim pod "mcp" wpis "robisie": { "type": "remote", "url": "${url}", "headers": { "Authorization": "Bearer {env:${KEY_ENV}}" } } albo uruchom init z --agent bez opencode.`);
|
|
161
|
+
}
|
|
162
|
+
return [
|
|
163
|
+
{ kind: "json", path: paths.opencodeJson, keyPath: ["mcp", MCP_SERVER_NAME], value: { type: "remote", url, enabled: true, headers: { Authorization: `Bearer {env:${KEY_ENV}}` } }, private: false },
|
|
164
|
+
...HELPER_SERVERS.map((name) => {
|
|
165
|
+
const { command, args } = helperCommand(name);
|
|
166
|
+
// Env is forwarded explicitly (`{env:…}`), and the first `npx` start may download the package (default timeout 5 s).
|
|
167
|
+
const environment = Object.fromEntries(HELPER_ENV.map((env) => [env, `{env:${env}}`]));
|
|
168
|
+
return { kind: "json", path: paths.opencodeJson, keyPath: ["mcp", name], value: { type: "local", command: [command, ...args], enabled: true, environment, timeout: 60_000 }, private: false };
|
|
169
|
+
}),
|
|
170
|
+
...roles.map((role) => ({ kind: "file", path: join(paths.opencodeDir, "agents", role.file), content: renderOpenCodeAgent(role) })),
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
/** Shell profile line that exports ROBISIE_KEY from the package's key file (roles and Codex/OpenCode read it). */
|
|
174
|
+
export function shellProfile(ctx) {
|
|
175
|
+
if (ctx.platform === "win32")
|
|
176
|
+
return null;
|
|
177
|
+
const shell = (ctx.env.SHELL ?? "").split("/").pop();
|
|
178
|
+
if (shell === "zsh")
|
|
179
|
+
return join(ctx.env.ZDOTDIR || ctx.home, ".zshrc");
|
|
180
|
+
if (shell === "bash")
|
|
181
|
+
return join(ctx.home, ctx.platform === "darwin" ? ".bash_profile" : ".bashrc");
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const SHELL_KEY_PATH = '"${XDG_CONFIG_HOME:-$HOME/.config}/robisie/key"';
|
|
185
|
+
export const SHELL_EXPORT = `[ -r ${SHELL_KEY_PATH} ] && export ${KEY_ENV}="$(cat ${SHELL_KEY_PATH})"`;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { AGENT_IDS, isAgentId } from "./agents.js";
|
|
5
|
+
import { doctor, init, uninstall } from "./commands.js";
|
|
6
|
+
import { HELPER_SERVERS, PACKAGE_VERSION } from "./constants.js";
|
|
7
|
+
import { RobisieError, errorMessage, nodeCtx } from "./context.js";
|
|
8
|
+
import { isHelperServer, runHelper } from "./helpers.js";
|
|
9
|
+
import { ROLE_IDS, ROLE_STEP } from "./manifest.js";
|
|
10
|
+
const HELP = `robisie ${PACKAGE_VERSION} — łączy Twojego agenta z tablicą robisie
|
|
11
|
+
|
|
12
|
+
npx robisie init klucz rob_… (z ${"ROBISIE_KEY"} albo z pytania) + konfiguracja MCP + role klienta
|
|
13
|
+
npx robisie doctor sprawdza połączenie, wersję kontraktu i role
|
|
14
|
+
npx robisie uninstall usuwa wpisy robisie i role; zachowuje kopie zapasowe
|
|
15
|
+
npx robisie mcp <${HELPER_SERVERS.join("|")}>
|
|
16
|
+
pomocnik ról (serwer MCP stdio) — uruchamia go agent, nie Ty
|
|
17
|
+
|
|
18
|
+
Opcje init:
|
|
19
|
+
--agent <${AGENT_IDS.join("|")}> wybrany dostawca (można powtórzyć albo podać po przecinku); razem z dostawcami mapy, bez --agent również wykryci
|
|
20
|
+
--role <rola=dostawca> wybór ceo, builder, quality → claude, codex, opencode (można powtórzyć)
|
|
21
|
+
--role=rola=dostawca ta sama składnia; np. --role=builder=codex zachowuje resztę mapy
|
|
22
|
+
--no-shell nie dopisuj ROBISIE_KEY do profilu powłoki
|
|
23
|
+
--key-stdin weź klucz z wejścia standardowego (np. printenv KLUCZ | npx robisie init --key-stdin)
|
|
24
|
+
`;
|
|
25
|
+
export function parseArgs(argv) {
|
|
26
|
+
const args = [...argv];
|
|
27
|
+
const command = args.shift() ?? "help";
|
|
28
|
+
const agents = [];
|
|
29
|
+
const roles = {};
|
|
30
|
+
let shell = true;
|
|
31
|
+
let keyStdin = false;
|
|
32
|
+
let target = null;
|
|
33
|
+
// Help wins over everything else, so `init --help` never starts an install.
|
|
34
|
+
if (args.includes("--help") || args.includes("-h"))
|
|
35
|
+
return { command, help: true, target, agents: null, roles, shell, keyStdin };
|
|
36
|
+
while (args.length > 0) {
|
|
37
|
+
const arg = args.shift();
|
|
38
|
+
if (arg === "--no-shell")
|
|
39
|
+
shell = false;
|
|
40
|
+
else if (arg === "--key-stdin")
|
|
41
|
+
keyStdin = true;
|
|
42
|
+
else if (arg === "--role" || arg.startsWith("--role=")) {
|
|
43
|
+
const value = arg === "--role" ? args.shift() ?? "" : arg.slice("--role=".length);
|
|
44
|
+
if (!value || value.startsWith("-"))
|
|
45
|
+
throw new RobisieError("Opcja --role wymaga przypisania rola=dostawca.", ROLE_STEP);
|
|
46
|
+
const [role, provider, extra] = value.split("=");
|
|
47
|
+
if (!ROLE_IDS.some((id) => id === role))
|
|
48
|
+
throw new RobisieError(`Nieznana rola „${role || "(pusta rola --role)"}”.`, ROLE_STEP);
|
|
49
|
+
if (extra !== undefined || !provider || !isAgentId(provider))
|
|
50
|
+
throw new RobisieError(`Niepoprawny dostawca „${provider ?? ""}” dla roli „${role}” (--role ${value}).`, ROLE_STEP);
|
|
51
|
+
const id = role;
|
|
52
|
+
if (roles[id] && roles[id] !== provider)
|
|
53
|
+
throw new RobisieError(`Sprzeczne przypisania roli „${role}”: ${roles[id]} i ${provider}.`, ROLE_STEP);
|
|
54
|
+
roles[id] = provider;
|
|
55
|
+
}
|
|
56
|
+
else if (arg === "--agent" || arg.startsWith("--agent=")) {
|
|
57
|
+
const value = arg === "--agent" ? args.shift() ?? "" : arg.slice("--agent=".length);
|
|
58
|
+
const ids = value.split(",").map((v) => v.trim());
|
|
59
|
+
// An empty value must not silently fall back to "every detected agent".
|
|
60
|
+
if (ids.some((id) => id === "") || value.startsWith("--")) {
|
|
61
|
+
throw new RobisieError("Opcja --agent wymaga nazwy agenta.", `Podaj np. \`--agent codex\` (dostępne: ${AGENT_IDS.join(", ")}) albo pomiń --agent, żeby użyć wykrytych.`);
|
|
62
|
+
}
|
|
63
|
+
for (const id of ids) {
|
|
64
|
+
if (!isAgentId(id))
|
|
65
|
+
throw new RobisieError(`Nieznany agent „${id}”.`, `Użyj jednego z: ${AGENT_IDS.join(", ")}.`);
|
|
66
|
+
if (!agents.includes(id))
|
|
67
|
+
agents.push(id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else if (command === "mcp" && target === null && !arg.startsWith("-"))
|
|
71
|
+
target = arg;
|
|
72
|
+
else
|
|
73
|
+
throw new RobisieError(`Nieznana opcja „${arg}”.`, "Zobacz `npx robisie help`.");
|
|
74
|
+
}
|
|
75
|
+
return { command, help: false, target, agents: agents.length > 0 ? agents : null, roles, shell, keyStdin };
|
|
76
|
+
}
|
|
77
|
+
export async function run(argv, ctx) {
|
|
78
|
+
try {
|
|
79
|
+
const args = parseArgs(argv);
|
|
80
|
+
if (args.help) {
|
|
81
|
+
ctx.out(HELP);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
switch (args.command) {
|
|
85
|
+
case "init":
|
|
86
|
+
await init(ctx, { agents: args.agents, roles: args.roles, shell: args.shell, keyStdin: args.keyStdin });
|
|
87
|
+
return 0;
|
|
88
|
+
case "doctor":
|
|
89
|
+
return await doctor(ctx);
|
|
90
|
+
case "uninstall":
|
|
91
|
+
return uninstall(ctx);
|
|
92
|
+
case "mcp":
|
|
93
|
+
if (!args.target || !isHelperServer(args.target)) {
|
|
94
|
+
throw new RobisieError(`Nieznany pomocnik „${args.target ?? ""}”.`, `Użyj jednego z: ${HELPER_SERVERS.join(", ")}.`);
|
|
95
|
+
}
|
|
96
|
+
return await runHelper(args.target);
|
|
97
|
+
case "--version":
|
|
98
|
+
case "version":
|
|
99
|
+
ctx.out(PACKAGE_VERSION);
|
|
100
|
+
return 0;
|
|
101
|
+
case "help":
|
|
102
|
+
case "--help":
|
|
103
|
+
case "-h":
|
|
104
|
+
ctx.out(HELP);
|
|
105
|
+
return 0;
|
|
106
|
+
default:
|
|
107
|
+
throw new RobisieError(`Nieznane polecenie „${args.command}”.`, "Zobacz `npx robisie help`.");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error instanceof RobisieError) {
|
|
112
|
+
ctx.err(`✗ ${error.message}`);
|
|
113
|
+
ctx.err(`Następny krok: ${error.nextStep}`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
ctx.err(`✗ Coś poszło nie tak: ${errorMessage(error)}`);
|
|
117
|
+
ctx.err("Następny krok: uruchom `npx robisie doctor`; jeśli błąd zostaje, napisz do robisie.");
|
|
118
|
+
}
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function isEntry() {
|
|
123
|
+
try {
|
|
124
|
+
return process.argv[1] !== undefined && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (isEntry()) {
|
|
131
|
+
run(process.argv.slice(2), nodeCtx()).then((code) => {
|
|
132
|
+
process.exitCode = code;
|
|
133
|
+
}, (error) => {
|
|
134
|
+
process.stderr.write(`✗ ${errorMessage(error)}\n`);
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
});
|
|
137
|
+
}
|