@orchestree/cli 2.2.0 → 3.5.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 +101 -0
- package/README.md +32 -503
- package/bin/crow.js +28 -0
- package/bin/orchestree.js +5 -8
- package/dist/gateways.d.ts +2 -0
- package/dist/gateways.js +57 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +163 -80
- package/dist/maintenance.d.ts +10 -0
- package/dist/maintenance.js +217 -0
- package/package.json +38 -40
- package/dist/commands/agents.d.ts +0 -1
- package/dist/commands/agents.js +0 -89
- package/dist/commands/chat.d.ts +0 -1
- package/dist/commands/chat.js +0 -33
- package/dist/commands/deploy.d.ts +0 -1
- package/dist/commands/deploy.js +0 -27
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +0 -32
- package/dist/commands/registry.d.ts +0 -1
- package/dist/commands/registry.js +0 -134
- package/dist/commands/run.d.ts +0 -2
- package/dist/commands/run.js +0 -67
- package/dist/commands/status.d.ts +0 -1
- package/dist/commands/status.js +0 -41
- package/dist/config.d.ts +0 -8
- package/dist/config.js +0 -38
package/dist/gateways.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// `crow gateways` — list model gateways and run setup wizards.
|
|
2
|
+
import { listGateways, loadCliConfig, systemExec, vertexSetup, } from '@orchestree/cli-core';
|
|
3
|
+
function pad(value, width) {
|
|
4
|
+
return value.length >= width ? value : value + ' '.repeat(width - value.length);
|
|
5
|
+
}
|
|
6
|
+
async function listCommand(io) {
|
|
7
|
+
const config = await loadCliConfig();
|
|
8
|
+
for (const gw of listGateways(config)) {
|
|
9
|
+
io.out(`${pad(gw.id, 15)} ${pad(gw.status, 13)} ${gw.detail}`);
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
function readFlag(args, name) {
|
|
14
|
+
const i = args.indexOf(name);
|
|
15
|
+
if (i === -1)
|
|
16
|
+
return { value: null, rest: args };
|
|
17
|
+
const value = args[i + 1];
|
|
18
|
+
if (!value || value.startsWith('--'))
|
|
19
|
+
return { value: null, rest: args };
|
|
20
|
+
return { value, rest: [...args.slice(0, i), ...args.slice(i + 2)] };
|
|
21
|
+
}
|
|
22
|
+
async function setupCommand(args, io) {
|
|
23
|
+
const id = args[0];
|
|
24
|
+
switch (id) {
|
|
25
|
+
case 'vertex': {
|
|
26
|
+
const { value: project, rest: afterProject } = readFlag(args.slice(1), '--project');
|
|
27
|
+
const { value: region, rest } = readFlag(afterProject, '--region');
|
|
28
|
+
const enableAnthropicModels = rest.includes('--enable-anthropic-models');
|
|
29
|
+
return vertexSetup({
|
|
30
|
+
project: project ?? undefined,
|
|
31
|
+
region: region ?? undefined,
|
|
32
|
+
enableAnthropicModels,
|
|
33
|
+
io,
|
|
34
|
+
exec: systemExec,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
case 'openrouter':
|
|
38
|
+
io.out('OpenRouter is already live — it is routed server-side via api.orchestree.ai and needs no local setup.');
|
|
39
|
+
return 0;
|
|
40
|
+
case 'azure_foundry':
|
|
41
|
+
// Honest-Beta: unbuilt capability says so and exits non-zero.
|
|
42
|
+
io.err('Azure AI Foundry support has not shipped yet — nothing was configured.');
|
|
43
|
+
return 1;
|
|
44
|
+
default:
|
|
45
|
+
io.err(`unknown gateway \`${id ?? ''}\` — known gateways: openrouter, vertex, azure_foundry`);
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function gatewaysCommand(args, io) {
|
|
50
|
+
const sub = args[0];
|
|
51
|
+
if (!sub || sub === 'list')
|
|
52
|
+
return listCommand(io);
|
|
53
|
+
if (sub === 'setup')
|
|
54
|
+
return setupCommand(args.slice(1), io);
|
|
55
|
+
io.err(`unknown gateways subcommand \`${sub}\` — use \`crow gateways\` or \`crow gateways setup <id>\``);
|
|
56
|
+
return 2;
|
|
57
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,88 +1,171 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
case 'orchestrate':
|
|
37
|
-
await orchestrateCommand(commandArgs);
|
|
38
|
-
break;
|
|
39
|
-
case 'help':
|
|
40
|
-
case '--help':
|
|
41
|
-
case '-h':
|
|
42
|
-
case undefined:
|
|
43
|
-
printHelp();
|
|
44
|
-
break;
|
|
45
|
-
case 'version':
|
|
46
|
-
case '--version':
|
|
47
|
-
case '-v':
|
|
48
|
-
console.log('orchestree v2.2.0');
|
|
49
|
-
break;
|
|
50
|
-
default:
|
|
51
|
-
console.error(`Unknown command: ${command}`);
|
|
52
|
-
printHelp();
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function printHelp() {
|
|
57
|
-
console.log(`
|
|
58
|
-
Orchestree CLI v2.2.0
|
|
1
|
+
// crow — the CrowCode CLI, the platform front door in your terminal.
|
|
2
|
+
// (`orchestree` remains as an alias for one major version.)
|
|
3
|
+
//
|
|
4
|
+
// crow login [--stdin] [--api <base>] sign in with an API key
|
|
5
|
+
// crow logout clear every credential backend
|
|
6
|
+
// crow whoami org · key name · scope
|
|
7
|
+
// crow gateways [setup <id>] model gateway status + setup
|
|
8
|
+
// crow crowcode [...] the CrowCode coding agent
|
|
9
|
+
// crow crowgent [...] fleet CLI (not shipped yet)
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { DEFAULT_API_BASE, crowPaint, loginCommand, logoutCommand, terminalIO, whoamiCommand, } from '@orchestree/cli-core';
|
|
14
|
+
import { gatewaysCommand } from './gateways.js';
|
|
15
|
+
import { completionsCommand, doctorCommand, updateCommand } from './maintenance.js';
|
|
16
|
+
// Same words on every terminal — crowPaint degrades to plain text when
|
|
17
|
+
// stdout is not a TTY or NO_COLOR is set, so piped `crow --help` stays clean.
|
|
18
|
+
function renderHelp() {
|
|
19
|
+
const p = crowPaint();
|
|
20
|
+
const cmd = (s) => p.accent(s);
|
|
21
|
+
const dim = (s) => p.dim(s);
|
|
22
|
+
return `${p.accentBright(p.bold('CrowCode CLI (crow)'))} — The Everything Platform in your terminal
|
|
23
|
+
|
|
24
|
+
${p.bold('usage:')}
|
|
25
|
+
${cmd('crow')} launch the CrowCode coding agent
|
|
26
|
+
${cmd('crow login')} [--stdin] [--api <base>] sign in with an Orchestree API key
|
|
27
|
+
${dim('(hidden prompt; --stdin reads the key from a pipe)')}
|
|
28
|
+
${cmd('crow logout')} sign out — clears every credential backend
|
|
29
|
+
${cmd('crow whoami')} show org · key name · scope for the active credential
|
|
30
|
+
${cmd('crow gateways')} list model gateways and their status
|
|
31
|
+
${cmd('crow gateways setup')} <id> [--project <p>] [--region <r>]
|
|
32
|
+
configure a gateway (vertex)
|
|
33
|
+
${cmd('crow crowcode')} [args...] launch the CrowCode coding agent
|
|
34
|
+
${cmd('crow crowgent')} [args...] fleet CLI — not shipped yet
|
|
35
|
+
${cmd('crow --version')} | ${cmd('crow --help')}
|
|
59
36
|
|
|
60
|
-
|
|
37
|
+
${p.bold('agent shortcuts')} ${dim('(forwarded to the CrowCode agent)')}:
|
|
38
|
+
${cmd('crow')} "fix the failing test" one-shot: run the task, print, exit
|
|
39
|
+
${cmd('crow -p')} "prompt" [--output-format plain|json|streaming-json]
|
|
40
|
+
${cmd('crow -c')} | ${cmd('crow -r')} [<id>] continue / resume a session
|
|
41
|
+
${cmd('crow models')} list model tiers
|
|
42
|
+
${cmd('crow sessions')} [search <q> | delete <id>]
|
|
43
|
+
${cmd('crow export')} <session-id> [file.md] session transcript as Markdown
|
|
61
44
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
agents types [b2c|b2b] List available agent types
|
|
67
|
-
agents create <slug> [name] Create a new agent
|
|
68
|
-
agents get <id> Get agent details
|
|
69
|
-
status Agent heartbeat overview
|
|
70
|
-
deploy <agent-id> <channel> Deploy agent to channel
|
|
71
|
-
registry search [options] Search 11K+ agent registry
|
|
72
|
-
registry info <slug> Get agent details from registry
|
|
73
|
-
registry stats Show registry statistics
|
|
74
|
-
run <slug> "message" [--stream] Execute a specific agent
|
|
75
|
-
orchestrate "message" [--stream] Auto-orchestrate with best agent
|
|
76
|
-
help Show this help
|
|
77
|
-
version Show version
|
|
45
|
+
${p.bold('maintenance:')}
|
|
46
|
+
${cmd('crow update')} [--check] update to the latest release
|
|
47
|
+
${cmd('crow doctor')} diagnose the install (PATH shadowing, CDN, API)
|
|
48
|
+
${cmd('crow completions')} <zsh|bash> print shell completions
|
|
78
49
|
|
|
79
|
-
|
|
80
|
-
|
|
50
|
+
${p.bold('auth notes:')}
|
|
51
|
+
${dim('·')} keys are stored in the OS keychain (0600 file fallback with a warning)
|
|
52
|
+
${dim('·')} ORCHESTREE_API_KEY overrides stored credentials — CI only; any agent
|
|
53
|
+
subprocess this CLI spawns can read the environment
|
|
54
|
+
${dim('·')} the key is never accepted as a command-line argument
|
|
81
55
|
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
${dim('alias: `orchestree` runs this same CLI (kept for one major version)')}
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
function versionString() {
|
|
60
|
+
try {
|
|
61
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
62
|
+
return pkg.version ?? '0.0.0';
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return '0.0.0';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function readFlag(args, name) {
|
|
69
|
+
const i = args.indexOf(name);
|
|
70
|
+
if (i === -1)
|
|
71
|
+
return { value: null, rest: args };
|
|
72
|
+
const value = args[i + 1];
|
|
73
|
+
if (!value || value.startsWith('--'))
|
|
74
|
+
return { value: null, rest: args };
|
|
75
|
+
return { value, rest: [...args.slice(0, i), ...args.slice(i + 2)] };
|
|
76
|
+
}
|
|
77
|
+
async function runCrowcode(args) {
|
|
78
|
+
const require = createRequire(import.meta.url);
|
|
79
|
+
let entry;
|
|
80
|
+
try {
|
|
81
|
+
entry = require.resolve('@orchestree/crowcode-cli/bin/crowcode.js');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
process.stderr.write('crowcode is not installed alongside this CLI — run `npm i -g @orchestree/cli`\n');
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
const child = spawn(process.execPath, [entry, ...args], { stdio: 'inherit' });
|
|
89
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async function main() {
|
|
93
|
+
const argv = process.argv.slice(2);
|
|
94
|
+
const cmd = argv[0];
|
|
95
|
+
const io = terminalIO();
|
|
96
|
+
if (!cmd) {
|
|
97
|
+
// Bare `crow` is the product: launch the CrowCode coding agent.
|
|
98
|
+
process.exitCode = await runCrowcode([]);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
|
|
102
|
+
process.stdout.write(renderHelp());
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
|
|
106
|
+
process.stdout.write(`crow ${versionString()}\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
switch (cmd) {
|
|
110
|
+
case 'login': {
|
|
111
|
+
const { value: apiBase, rest } = readFlag(argv.slice(1), '--api');
|
|
112
|
+
const stdin = rest.includes('--stdin');
|
|
113
|
+
const leftovers = rest.filter((a) => a !== '--stdin');
|
|
114
|
+
if (leftovers.length > 0) {
|
|
115
|
+
// The key itself is NEVER accepted via argv.
|
|
116
|
+
io.err('crow login does not accept the key as an argument — it would leak into shell history and process lists.');
|
|
117
|
+
io.err('Use the hidden prompt (`crow login`) or pipe it: `crow login --stdin < keyfile`');
|
|
118
|
+
process.exitCode = 2;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
process.exitCode = await loginCommand({
|
|
122
|
+
apiBase: apiBase ?? DEFAULT_API_BASE,
|
|
123
|
+
stdin,
|
|
124
|
+
io,
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
case 'logout':
|
|
129
|
+
process.exitCode = await logoutCommand({ apiBase: DEFAULT_API_BASE, io });
|
|
130
|
+
return;
|
|
131
|
+
case 'whoami': {
|
|
132
|
+
const { value: apiBase } = readFlag(argv.slice(1), '--api');
|
|
133
|
+
process.exitCode = await whoamiCommand({ apiBase: apiBase ?? DEFAULT_API_BASE, io });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case 'gateways':
|
|
137
|
+
process.exitCode = await gatewaysCommand(argv.slice(1), io);
|
|
138
|
+
return;
|
|
139
|
+
case 'crowcode':
|
|
140
|
+
process.exitCode = await runCrowcode(argv.slice(1));
|
|
141
|
+
return;
|
|
142
|
+
case 'models':
|
|
143
|
+
case 'sessions':
|
|
144
|
+
case 'export':
|
|
145
|
+
// Session/model management lives in the agent — forward verbatim.
|
|
146
|
+
process.exitCode = await runCrowcode(argv);
|
|
147
|
+
return;
|
|
148
|
+
case 'update':
|
|
149
|
+
process.exitCode = await updateCommand(argv.slice(1), versionString(), io);
|
|
150
|
+
return;
|
|
151
|
+
case 'doctor':
|
|
152
|
+
process.exitCode = await doctorCommand(versionString(), io);
|
|
153
|
+
return;
|
|
154
|
+
case 'completions':
|
|
155
|
+
process.exitCode = completionsCommand(argv[1], io);
|
|
156
|
+
return;
|
|
157
|
+
case 'crowgent':
|
|
158
|
+
// Honest-Beta: unbuilt capability says so and exits non-zero.
|
|
159
|
+
io.err('the crowgent fleet CLI has not shipped yet (Wave D of the CLI suite) — nothing was run');
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
return;
|
|
162
|
+
default:
|
|
163
|
+
// Everything else — prompts (`crow "fix the test"`) and agent flags
|
|
164
|
+
// (`crow -p … --output-format json`, `crow -c`) — is the agent's.
|
|
165
|
+
process.exitCode = await runCrowcode(argv);
|
|
166
|
+
}
|
|
84
167
|
}
|
|
85
168
|
main().catch((err) => {
|
|
86
|
-
|
|
169
|
+
process.stderr.write(`${err.message}\n`);
|
|
87
170
|
process.exit(1);
|
|
88
171
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type CliIO } from '@orchestree/cli-core';
|
|
2
|
+
export declare const CDN_LATEST_URL = "https://media.orchestree.ai/cli/latest";
|
|
3
|
+
export declare const INSTALLER_URL = "https://orchestree.ai/crow";
|
|
4
|
+
/** Numeric semver compare: >0 when a is newer than b. */
|
|
5
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
6
|
+
/** Every `crow` executable reachable through PATH, in PATH order. */
|
|
7
|
+
export declare function pathShadows(binName?: string): string[];
|
|
8
|
+
export declare function updateCommand(args: string[], currentVersion: string, io: CliIO): Promise<number>;
|
|
9
|
+
export declare function doctorCommand(currentVersion: string, io: CliIO): Promise<number>;
|
|
10
|
+
export declare function completionsCommand(shell: string | undefined, io: CliIO): number;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Self-maintenance: `crow update`, `crow doctor`, `crow completions`.
|
|
2
|
+
// Everything reports what it actually observed — no simulated checks.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { accessSync, constants, realpathSync } from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { DEFAULT_API_BASE, crowPaint } from '@orchestree/cli-core';
|
|
9
|
+
export const CDN_LATEST_URL = 'https://media.orchestree.ai/cli/latest';
|
|
10
|
+
export const INSTALLER_URL = 'https://orchestree.ai/crow';
|
|
11
|
+
async function fetchText(url, timeoutMs = 8_000) {
|
|
12
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
throw new Error(`${url} → HTTP ${res.status}`);
|
|
15
|
+
return (await res.text()).trim();
|
|
16
|
+
}
|
|
17
|
+
/** Numeric semver compare: >0 when a is newer than b. */
|
|
18
|
+
export function compareVersions(a, b) {
|
|
19
|
+
const pa = a.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
20
|
+
const pb = b.split('.').map((n) => Number.parseInt(n, 10) || 0);
|
|
21
|
+
for (let i = 0; i < 3; i++) {
|
|
22
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
23
|
+
if (d !== 0)
|
|
24
|
+
return d;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
/** The real on-disk home of the code that is currently running. */
|
|
29
|
+
function installOrigin() {
|
|
30
|
+
try {
|
|
31
|
+
return realpathSync(fileURLToPath(import.meta.url));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return process.argv[1] ?? '(unknown)';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Every `crow` executable reachable through PATH, in PATH order. */
|
|
38
|
+
export function pathShadows(binName = 'crow') {
|
|
39
|
+
const dirs = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
40
|
+
const found = [];
|
|
41
|
+
for (const dir of dirs) {
|
|
42
|
+
const candidate = path.join(dir, binName);
|
|
43
|
+
try {
|
|
44
|
+
accessSync(candidate, constants.X_OK);
|
|
45
|
+
const real = realpathSync(candidate);
|
|
46
|
+
if (!found.includes(real))
|
|
47
|
+
found.push(real);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// not here — keep walking PATH
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
export async function updateCommand(args, currentVersion, io) {
|
|
56
|
+
const checkOnly = args.includes('--check');
|
|
57
|
+
let latest;
|
|
58
|
+
try {
|
|
59
|
+
latest = await fetchText(CDN_LATEST_URL);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
io.err(`could not reach the release CDN (${CDN_LATEST_URL}): ${err.message}`);
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
if (!/^\d+\.\d+\.\d+$/.test(latest)) {
|
|
66
|
+
io.err(`release CDN returned an unexpected version pointer: ${JSON.stringify(latest.slice(0, 40))}`);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
const behind = compareVersions(latest, currentVersion) > 0;
|
|
70
|
+
if (!behind) {
|
|
71
|
+
io.out(`crow ${currentVersion} is up to date (latest: ${latest})`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
io.out(`update available: crow ${currentVersion} → ${latest}`);
|
|
75
|
+
if (checkOnly) {
|
|
76
|
+
io.out('run `crow update` to install it');
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const origin = installOrigin();
|
|
80
|
+
const crowHome = path.join(os.homedir(), '.crow');
|
|
81
|
+
if (!origin.startsWith(crowHome)) {
|
|
82
|
+
io.err(`note: this copy runs from ${origin}, not ${crowHome} — the installer refreshes ${crowHome}; run \`crow doctor\` if the old copy keeps winning on PATH`);
|
|
83
|
+
}
|
|
84
|
+
io.out(`running the installer: curl -fsSL ${INSTALLER_URL} | sh`);
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const child = spawn('sh', ['-c', `curl -fsSL ${INSTALLER_URL} | sh`], { stdio: 'inherit' });
|
|
87
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export async function doctorCommand(currentVersion, io) {
|
|
91
|
+
const p = crowPaint();
|
|
92
|
+
const pass = (label, detail) => io.out(` ${p.ok('✓')} ${p.bold(label.padEnd(16))} ${detail}`);
|
|
93
|
+
const warn = (label, detail) => io.out(` ${p.warn('!')} ${p.bold(label.padEnd(16))} ${detail}`);
|
|
94
|
+
const fail = (label, detail) => io.out(` ${p.err('✗')} ${p.bold(label.padEnd(16))} ${detail}`);
|
|
95
|
+
let problems = 0;
|
|
96
|
+
// 1. Node runtime
|
|
97
|
+
const major = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
98
|
+
if (major >= 18)
|
|
99
|
+
pass('node', `v${process.versions.node} (${process.execPath})`);
|
|
100
|
+
else {
|
|
101
|
+
fail('node', `v${process.versions.node} — crow needs Node >= 18`);
|
|
102
|
+
problems++;
|
|
103
|
+
}
|
|
104
|
+
// 2. Install origin
|
|
105
|
+
const origin = installOrigin();
|
|
106
|
+
const crowHome = path.join(os.homedir(), '.crow');
|
|
107
|
+
if (origin.startsWith(crowHome))
|
|
108
|
+
pass('install', origin);
|
|
109
|
+
else
|
|
110
|
+
warn('install', `${origin} — not the standard ${crowHome} install (npm link or npm -g?)`);
|
|
111
|
+
// 3. PATH shadowing — the exact failure mode behind "old interface" bugs.
|
|
112
|
+
const shadows = pathShadows();
|
|
113
|
+
if (shadows.length === 0) {
|
|
114
|
+
warn('path', '`crow` is not on PATH (running via an absolute path?)');
|
|
115
|
+
}
|
|
116
|
+
else if (shadows.length === 1) {
|
|
117
|
+
pass('path', shadows[0]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
fail('path', `${shadows.length} different \`crow\` binaries on PATH — the first one wins:`);
|
|
121
|
+
for (const s of shadows)
|
|
122
|
+
io.out(` ${s === shadows[0] ? p.warn('→') : ' '} ${s}`);
|
|
123
|
+
io.out(` remove stale copies (e.g. \`npm rm -g @orchestree/cli\`) so ${crowHome}/bin wins`);
|
|
124
|
+
problems++;
|
|
125
|
+
}
|
|
126
|
+
// 4. Release CDN + version currency
|
|
127
|
+
try {
|
|
128
|
+
const latest = await fetchText(CDN_LATEST_URL);
|
|
129
|
+
if (compareVersions(latest, currentVersion) > 0) {
|
|
130
|
+
warn('version', `${currentVersion} installed, ${latest} available — run \`crow update\``);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
pass('version', `${currentVersion} (latest: ${latest})`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
fail('cdn', `${CDN_LATEST_URL} unreachable: ${err.message}`);
|
|
138
|
+
problems++;
|
|
139
|
+
}
|
|
140
|
+
// 5. API reachability — any HTTP answer proves the wire works.
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`${DEFAULT_API_BASE}/health`, {
|
|
143
|
+
signal: AbortSignal.timeout(8_000),
|
|
144
|
+
});
|
|
145
|
+
pass('api', `${DEFAULT_API_BASE} reachable (HTTP ${res.status})`);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
fail('api', `${DEFAULT_API_BASE} unreachable: ${err.message}`);
|
|
149
|
+
problems++;
|
|
150
|
+
}
|
|
151
|
+
io.out('');
|
|
152
|
+
io.out(problems === 0 ? p.ok('no problems found') : p.err(`${problems} problem${problems === 1 ? '' : 's'} found`));
|
|
153
|
+
return problems === 0 ? 0 : 1;
|
|
154
|
+
}
|
|
155
|
+
const SUBCOMMANDS = 'login logout whoami gateways crowcode models sessions export update doctor completions help version';
|
|
156
|
+
const ZSH_COMPLETION = `#compdef crow
|
|
157
|
+
# crow zsh completion — generated by \`crow completions zsh\`
|
|
158
|
+
# install: crow completions zsh > "\${fpath[1]}/_crow" && autoload -Uz compinit && compinit
|
|
159
|
+
_crow() {
|
|
160
|
+
local -a subcmds
|
|
161
|
+
subcmds=(
|
|
162
|
+
'login:sign in with an Orchestree API key'
|
|
163
|
+
'logout:clear stored credentials'
|
|
164
|
+
'whoami:show the active credential'
|
|
165
|
+
'gateways:model gateway status + setup'
|
|
166
|
+
'crowcode:launch the CrowCode coding agent'
|
|
167
|
+
'models:list model tiers'
|
|
168
|
+
'sessions:list, search, or delete sessions'
|
|
169
|
+
'export:export a session transcript as Markdown'
|
|
170
|
+
'update:update crow to the latest release'
|
|
171
|
+
'doctor:diagnose the installation'
|
|
172
|
+
'completions:print shell completions'
|
|
173
|
+
)
|
|
174
|
+
if (( CURRENT == 2 )); then
|
|
175
|
+
_describe 'crow command' subcmds
|
|
176
|
+
return
|
|
177
|
+
fi
|
|
178
|
+
case "\${words[2]}" in
|
|
179
|
+
sessions) compadd list search delete ;;
|
|
180
|
+
completions) compadd zsh bash ;;
|
|
181
|
+
update) compadd -- --check ;;
|
|
182
|
+
gateways) compadd setup ;;
|
|
183
|
+
esac
|
|
184
|
+
}
|
|
185
|
+
_crow "$@"
|
|
186
|
+
`;
|
|
187
|
+
const BASH_COMPLETION = `# crow bash completion — generated by \`crow completions bash\`
|
|
188
|
+
# install: crow completions bash >> ~/.bashrc (or into bash_completion.d)
|
|
189
|
+
_crow_completions() {
|
|
190
|
+
local cur prev
|
|
191
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
192
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
193
|
+
if [ "\$COMP_CWORD" -eq 1 ]; then
|
|
194
|
+
COMPREPLY=( \$(compgen -W "${SUBCOMMANDS}" -- "\$cur") )
|
|
195
|
+
return
|
|
196
|
+
fi
|
|
197
|
+
case "\$prev" in
|
|
198
|
+
sessions) COMPREPLY=( \$(compgen -W "list search delete" -- "\$cur") ) ;;
|
|
199
|
+
completions) COMPREPLY=( \$(compgen -W "zsh bash" -- "\$cur") ) ;;
|
|
200
|
+
update) COMPREPLY=( \$(compgen -W "--check" -- "\$cur") ) ;;
|
|
201
|
+
gateways) COMPREPLY=( \$(compgen -W "setup" -- "\$cur") ) ;;
|
|
202
|
+
esac
|
|
203
|
+
}
|
|
204
|
+
complete -F _crow_completions crow
|
|
205
|
+
`;
|
|
206
|
+
export function completionsCommand(shell, io) {
|
|
207
|
+
if (shell === 'zsh') {
|
|
208
|
+
process.stdout.write(ZSH_COMPLETION);
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
if (shell === 'bash') {
|
|
212
|
+
process.stdout.write(BASH_COMPLETION);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
io.err('usage: crow completions <zsh|bash>');
|
|
216
|
+
return 2;
|
|
217
|
+
}
|
package/package.json
CHANGED
|
@@ -1,57 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchestree/cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.5.0",
|
|
4
|
+
"description": "CrowCode CLI (crow) — the Orchestree platform in your terminal: sign in, inspect your identity, manage model gateways, and launch the suite's agents. Installs the `crow` command (`orchestree` remains as an alias).",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/multivitaminds/Orchestree.git",
|
|
13
|
+
"directory": "apps/orchestree-cli"
|
|
13
14
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist",
|
|
16
|
-
"README.md",
|
|
17
|
-
"LICENSE",
|
|
18
|
-
"bin"
|
|
19
|
-
],
|
|
20
15
|
"keywords": [
|
|
16
|
+
"crow",
|
|
17
|
+
"crowcode",
|
|
21
18
|
"orchestree",
|
|
22
19
|
"cli",
|
|
23
20
|
"command-line",
|
|
24
21
|
"devtools"
|
|
25
22
|
],
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"repository": {
|
|
30
|
-
"type": "git",
|
|
31
|
-
"url": "https://github.com/orchestree/orchestree.git",
|
|
32
|
-
"directory": "packages/cli"
|
|
33
|
-
},
|
|
34
|
-
"bugs": {
|
|
35
|
-
"url": "https://github.com/orchestree/orchestree/issues"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsc --outDir dist",
|
|
39
|
-
"dev": "tsc --outDir dist --watch",
|
|
40
|
-
"start": "node bin/orchestree.js",
|
|
41
|
-
"test": "echo \"Tests passed\" && exit 0",
|
|
42
|
-
"clean": "rm -rf dist",
|
|
43
|
-
"prepublishOnly": "npm run build"
|
|
23
|
+
"bin": {
|
|
24
|
+
"crow": "./bin/crow.js",
|
|
25
|
+
"orchestree": "./bin/orchestree.js"
|
|
44
26
|
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin",
|
|
29
|
+
"dist",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
45
32
|
"engines": {
|
|
46
|
-
"node": ">=
|
|
33
|
+
"node": ">=20"
|
|
47
34
|
},
|
|
48
|
-
"
|
|
49
|
-
"
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "tsx src/index.ts",
|
|
37
|
+
"start": "node dist/index.js",
|
|
38
|
+
"build": "tsc -p tsconfig.json",
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"test:run": "vitest run",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
42
|
+
"lint": "echo 'No lint configured for orchestree-cli yet'"
|
|
50
43
|
},
|
|
51
44
|
"dependencies": {
|
|
52
|
-
"@orchestree/
|
|
45
|
+
"@orchestree/cli-core": "0.2.0",
|
|
46
|
+
"@orchestree/crowcode-cli": "0.5.0"
|
|
53
47
|
},
|
|
54
|
-
"
|
|
55
|
-
"orchestree": "
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@orchestree/typescript-config": "0.0.0",
|
|
50
|
+
"@types/node": "^24.10.1",
|
|
51
|
+
"tsx": "^4.20.0",
|
|
52
|
+
"typescript": "~5.9.3",
|
|
53
|
+
"vitest": "^4.0.18"
|
|
56
54
|
}
|
|
57
|
-
}
|
|
55
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function agentsCommand(args: string[]): Promise<void>;
|