@orchagent/cli 0.3.94 → 0.3.96
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/dist/commands/dag.js +3 -1
- package/dist/commands/fork.js +35 -1
- package/dist/commands/logs.js +6 -1
- package/dist/commands/run.js +28 -4
- package/dist/commands/templates/github-weekly-summary.js +17 -0
- package/dist/commands/test.js +15 -2
- package/dist/commands/validate.js +13 -2
- package/dist/lib/doctor/output.js +4 -0
- package/dist/lib/errors.js +26 -2
- package/package.json +1 -1
package/dist/commands/dag.js
CHANGED
|
@@ -217,7 +217,9 @@ function registerDagCommand(program) {
|
|
|
217
217
|
spinner.stop();
|
|
218
218
|
const msg = e instanceof Error ? e.message : 'Unknown error';
|
|
219
219
|
if (msg.includes('404') || msg.includes('not found') || msg.includes('Not part of')) {
|
|
220
|
-
throw new errors_1.CliError(`Run ${resolvedRunId.slice(0, 8)}... is not part of an orchestration chain
|
|
220
|
+
throw new errors_1.CliError(`Run ${resolvedRunId.slice(0, 8)}... is not part of an orchestration chain.\n\n` +
|
|
221
|
+
`The DAG view is only available for runs that call other agents via the SDK.\n` +
|
|
222
|
+
`Verify that your agent uses the Orchestrator SDK to invoke other agents.`);
|
|
221
223
|
}
|
|
222
224
|
throw e;
|
|
223
225
|
}
|
package/dist/commands/fork.js
CHANGED
|
@@ -33,6 +33,24 @@ async function resolveWorkspace(config, workspaceSlug) {
|
|
|
33
33
|
}
|
|
34
34
|
return workspace;
|
|
35
35
|
}
|
|
36
|
+
async function resolveCurrentWorkspace(config) {
|
|
37
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
38
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
39
|
+
// If user has a default workspace configured, use it
|
|
40
|
+
if (configFile.workspace) {
|
|
41
|
+
const workspace = response.workspaces.find((w) => w.slug === configFile.workspace);
|
|
42
|
+
if (workspace) {
|
|
43
|
+
return workspace;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// If only one workspace, use it
|
|
47
|
+
if (response.workspaces.length === 1) {
|
|
48
|
+
return response.workspaces[0];
|
|
49
|
+
}
|
|
50
|
+
// Multiple workspaces and no default — this shouldn't happen in fork flow,
|
|
51
|
+
// but if we get here, throw an error
|
|
52
|
+
throw new errors_1.CliError('Multiple workspaces available. Use `orch workspace use <slug>` to set default or `--workspace <slug>` to specify.');
|
|
53
|
+
}
|
|
36
54
|
function registerForkCommand(program) {
|
|
37
55
|
program
|
|
38
56
|
.command('fork <agent>')
|
|
@@ -101,7 +119,23 @@ Examples:
|
|
|
101
119
|
return;
|
|
102
120
|
}
|
|
103
121
|
const forked = result.agent;
|
|
104
|
-
|
|
122
|
+
// Resolve the target workspace slug:
|
|
123
|
+
// 1. If org_slug is in response, use it (gateway knows what org it created the agent in)
|
|
124
|
+
// 2. If explicit --workspace was provided, use its slug
|
|
125
|
+
// 3. Otherwise, resolve current workspace
|
|
126
|
+
let targetOrgSlug;
|
|
127
|
+
if (forked.org_slug) {
|
|
128
|
+
targetOrgSlug = forked.org_slug;
|
|
129
|
+
}
|
|
130
|
+
else if (targetWorkspace) {
|
|
131
|
+
targetOrgSlug = targetWorkspace.slug;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
write('Resolving current workspace...\n');
|
|
135
|
+
const currentWorkspace = await resolveCurrentWorkspace(config);
|
|
136
|
+
targetOrgSlug = currentWorkspace.slug;
|
|
137
|
+
targetWorkspace = currentWorkspace;
|
|
138
|
+
}
|
|
105
139
|
write(`\n${chalk_1.default.green('\u2713')} Forked ${org}/${agent}@${version}\n`);
|
|
106
140
|
write(` New agent: ${targetOrgSlug}/${forked.name}@${forked.version}\n`);
|
|
107
141
|
if (targetWorkspace) {
|
package/dist/commands/logs.js
CHANGED
|
@@ -31,7 +31,12 @@ async function resolveWorkspaceId(config, slug) {
|
|
|
31
31
|
if (response.workspaces.length === 1) {
|
|
32
32
|
return response.workspaces[0].id;
|
|
33
33
|
}
|
|
34
|
-
// Multiple workspaces —
|
|
34
|
+
// Multiple workspaces — try to default to personal workspace
|
|
35
|
+
const personalWorkspace = response.workspaces.find((w) => w.type === 'personal');
|
|
36
|
+
if (personalWorkspace) {
|
|
37
|
+
return personalWorkspace.id;
|
|
38
|
+
}
|
|
39
|
+
// Multiple workspaces and no personal workspace found — ask the user to pick
|
|
35
40
|
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
36
41
|
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
37
42
|
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
package/dist/commands/run.js
CHANGED
|
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.localCommandForEntrypoint = localCommandForEntrypoint;
|
|
40
|
+
exports.canonicalAgentType = canonicalAgentType;
|
|
40
41
|
exports.inferFileField = inferFileField;
|
|
41
42
|
exports.validateInputSchema = validateInputSchema;
|
|
42
43
|
exports.tryParseJsonObject = tryParseJsonObject;
|
|
@@ -97,7 +98,21 @@ function parseAgentRef(value) {
|
|
|
97
98
|
}
|
|
98
99
|
function canonicalAgentType(typeValue) {
|
|
99
100
|
const normalized = (typeValue || 'agent').toLowerCase();
|
|
100
|
-
|
|
101
|
+
// Handle legacy type names: agentic → agent, code → tool
|
|
102
|
+
if (normalized === 'agentic')
|
|
103
|
+
return 'agent';
|
|
104
|
+
if (normalized === 'code')
|
|
105
|
+
return 'tool';
|
|
106
|
+
// Return the canonical type, defaulting to 'agent' for unrecognized types
|
|
107
|
+
if (normalized === 'prompt')
|
|
108
|
+
return 'prompt';
|
|
109
|
+
if (normalized === 'tool')
|
|
110
|
+
return 'tool';
|
|
111
|
+
if (normalized === 'agent')
|
|
112
|
+
return 'agent';
|
|
113
|
+
if (normalized === 'skill')
|
|
114
|
+
return 'skill';
|
|
115
|
+
return 'agent';
|
|
101
116
|
}
|
|
102
117
|
function resolveExecutionEngine(agentData) {
|
|
103
118
|
if (agentData.execution_engine === 'direct_llm' || agentData.execution_engine === 'managed_loop' || agentData.execution_engine === 'code_runtime') {
|
|
@@ -1900,7 +1915,8 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1900
1915
|
}
|
|
1901
1916
|
}
|
|
1902
1917
|
// --estimate: show cost estimate before running and ask for confirmation
|
|
1903
|
-
|
|
1918
|
+
// --estimate-only: show cost estimate and exit without running
|
|
1919
|
+
if (options.estimate || options.estimateOnly) {
|
|
1904
1920
|
try {
|
|
1905
1921
|
const est = await (0, api_1.getAgentCostEstimate)(resolved, org, parsed.agent, parsed.version);
|
|
1906
1922
|
const e = est.estimate;
|
|
@@ -1926,7 +1942,11 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1926
1942
|
}
|
|
1927
1943
|
process.stderr.write('\n');
|
|
1928
1944
|
}
|
|
1929
|
-
//
|
|
1945
|
+
// If --estimate-only, exit after showing estimate
|
|
1946
|
+
if (options.estimateOnly) {
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
// Otherwise, ask for confirmation (--estimate only)
|
|
1930
1950
|
const rl = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
1931
1951
|
const iface = rl.createInterface({ input: process.stdin, output: process.stderr });
|
|
1932
1952
|
const answer = await new Promise(resolve => {
|
|
@@ -1939,7 +1959,10 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1939
1959
|
}
|
|
1940
1960
|
}
|
|
1941
1961
|
catch {
|
|
1942
|
-
// Non-fatal: if estimate fails, proceed with the run
|
|
1962
|
+
// Non-fatal: if estimate fails, proceed with the run (or exit if --estimate-only)
|
|
1963
|
+
if (options.estimateOnly) {
|
|
1964
|
+
throw new errors_1.CliError('Could not fetch cost estimate.');
|
|
1965
|
+
}
|
|
1943
1966
|
process.stderr.write(chalk_1.default.gray('Could not fetch cost estimate. Proceeding...\n\n'));
|
|
1944
1967
|
}
|
|
1945
1968
|
}
|
|
@@ -2864,6 +2887,7 @@ function registerRunCommand(program) {
|
|
|
2864
2887
|
.option('--json', 'Output raw JSON')
|
|
2865
2888
|
.option('--verbose', 'Show sandbox stdout/stderr and debug info (cloud only)')
|
|
2866
2889
|
.option('--estimate', 'Show cost estimate before running and ask for confirmation')
|
|
2890
|
+
.option('--estimate-only', 'Show cost estimate and exit (for CI/CD automation)')
|
|
2867
2891
|
.option('--provider <provider>', 'LLM provider (openai, anthropic, gemini, ollama)')
|
|
2868
2892
|
.option('--model <model>', 'LLM model to use (overrides agent default)')
|
|
2869
2893
|
.option('--key <key>', 'LLM API key (overrides env vars)')
|
|
@@ -628,13 +628,27 @@ class Analyst:
|
|
|
628
628
|
|
|
629
629
|
async def generate_weekly_summary(self, store: ActivityStore) -> str:
|
|
630
630
|
"""Generate an intelligent weekly summary from the activity window."""
|
|
631
|
+
from datetime import datetime, timezone
|
|
632
|
+
|
|
631
633
|
activity_data = store.serialise_for_llm()
|
|
632
634
|
repos = ", ".join(store.repos)
|
|
635
|
+
now = datetime.now(timezone.utc)
|
|
636
|
+
current_date = now.strftime("%d %b %Y")
|
|
637
|
+
|
|
638
|
+
# Period range from activity window
|
|
639
|
+
period_start = store.window.period_start.strftime("%d %b %Y") if store.window and store.window.period_start else "unknown"
|
|
640
|
+
period_end = store.window.period_end.strftime("%d %b %Y") if store.window and store.window.period_end else current_date
|
|
633
641
|
|
|
634
642
|
system_prompt = self._summary_prompt.replace(
|
|
635
643
|
"{team_name}", self.team_name
|
|
636
644
|
).replace(
|
|
637
645
|
"{repos}", repos
|
|
646
|
+
).replace(
|
|
647
|
+
"{current_date}", current_date
|
|
648
|
+
).replace(
|
|
649
|
+
"{period_start}", period_start
|
|
650
|
+
).replace(
|
|
651
|
+
"{period_end}", period_end
|
|
638
652
|
).replace(
|
|
639
653
|
"{activity_data}", activity_data
|
|
640
654
|
)
|
|
@@ -733,6 +747,9 @@ anthropic>=0.40.0
|
|
|
733
747
|
// ─── prompts/weekly_summary.md ───────────────────────────────────────────────
|
|
734
748
|
exports.TEMPLATE_WEEKLY_SUMMARY_PROMPT = `You are a senior engineering manager analysing your team's GitHub activity for the past week. Your job is to write a concise, insightful weekly summary that a CTO or team lead would actually want to read on Monday morning.
|
|
735
749
|
|
|
750
|
+
**Current date: {current_date}**
|
|
751
|
+
**Reporting period: {period_start} to {period_end}**
|
|
752
|
+
|
|
736
753
|
Rules:
|
|
737
754
|
- INTERPRET, don't just list. "3 PRs merged" is useless. "The auth refactor shipped -- 3 PRs merged across 2 repos" is useful.
|
|
738
755
|
- Highlight what SHIPPED (merged PRs, significant commits). This is the headline.
|
package/dist/commands/test.js
CHANGED
|
@@ -1061,25 +1061,32 @@ async function runOnce(agentDir, dataJson, verbose, config) {
|
|
|
1061
1061
|
function registerTestCommand(program) {
|
|
1062
1062
|
program
|
|
1063
1063
|
.command('test [path]')
|
|
1064
|
-
.description('Validate
|
|
1064
|
+
.description('Validate configuration and run test suite (fixtures + unit tests)')
|
|
1065
1065
|
.option('-v, --verbose', 'Show detailed test output')
|
|
1066
1066
|
.option('-w, --watch', 'Watch for file changes and re-run tests')
|
|
1067
1067
|
.option('-r, --run', 'Run the agent once with --data input (validate first)')
|
|
1068
1068
|
.option('-d, --data <json>', 'JSON input for --run mode')
|
|
1069
|
+
.option('--validate-only', 'Run validation only (skip test suite)')
|
|
1069
1070
|
.addHelpText('after', `
|
|
1070
1071
|
Examples:
|
|
1071
1072
|
orch test Validate + run tests in current directory
|
|
1072
1073
|
orch test ./my-agent Validate + run tests in specified directory
|
|
1073
1074
|
orch test --verbose Show detailed test output
|
|
1074
1075
|
orch test --watch Watch mode — re-run on file changes
|
|
1076
|
+
orch test --validate-only Validation only (same as: orch validate)
|
|
1075
1077
|
orch test --run --data '{"task": "hello"}' Validate, then run once
|
|
1076
1078
|
|
|
1077
|
-
What it
|
|
1079
|
+
What it does (default):
|
|
1078
1080
|
1. Validates orchagent.json (type, engine, required files, secrets, etc.)
|
|
1079
1081
|
2. Runs Python tests (pytest): test_*.py, *_test.py
|
|
1080
1082
|
3. Runs JS/TS tests (vitest): *.test.ts, *.spec.ts
|
|
1081
1083
|
4. Runs fixture tests: tests/fixture-*.json
|
|
1082
1084
|
|
|
1085
|
+
When to use each command:
|
|
1086
|
+
orch validate Quick validation before publishing (config only)
|
|
1087
|
+
orch test Full test suite (config + fixtures + unit tests)
|
|
1088
|
+
orch test --validate-only Same as validate (config only)
|
|
1089
|
+
|
|
1083
1090
|
Fixture Format (tests/fixture-basic.json):
|
|
1084
1091
|
{
|
|
1085
1092
|
"description": "Test description",
|
|
@@ -1136,6 +1143,12 @@ Run mode (--run):
|
|
|
1136
1143
|
catch {
|
|
1137
1144
|
// Config not available, fixture tests will use env vars only
|
|
1138
1145
|
}
|
|
1146
|
+
// --validate-only flag: run validation then exit
|
|
1147
|
+
if (options.validateOnly) {
|
|
1148
|
+
const validation = await validateAgent(agentDir);
|
|
1149
|
+
const isValid = printValidation(validation);
|
|
1150
|
+
process.exit(isValid ? 0 : 1);
|
|
1151
|
+
}
|
|
1139
1152
|
// Run mode: validate then execute once
|
|
1140
1153
|
if (options.run) {
|
|
1141
1154
|
if (!options.data) {
|
|
@@ -41,7 +41,7 @@ function printResult(result, serverIssues) {
|
|
|
41
41
|
// Header
|
|
42
42
|
const label = m.isSkill ? 'skill' : 'agent';
|
|
43
43
|
const name = m.agentName || '(unknown)';
|
|
44
|
-
process.stderr.write(`\
|
|
44
|
+
process.stderr.write(`\n${chalk_1.default.dim('[validation only]')} Validating ${label}: ${chalk_1.default.bold(name)}\n\n`);
|
|
45
45
|
// Summary line: type, engine, mode
|
|
46
46
|
if (!m.isSkill && m.agentType && m.executionEngine) {
|
|
47
47
|
process.stderr.write(chalk_1.default.dim(` Type: ${m.agentType} (${m.executionEngine}), Run mode: ${m.runMode || 'on_demand'}\n\n`));
|
|
@@ -115,7 +115,18 @@ function registerValidateCommand(program) {
|
|
|
115
115
|
program
|
|
116
116
|
.command('validate')
|
|
117
117
|
.alias('lint')
|
|
118
|
-
.description('Validate
|
|
118
|
+
.description('Validate configuration only (no tests)')
|
|
119
|
+
.addHelpText('after', `
|
|
120
|
+
Use 'orch validate' to check configuration before publishing.
|
|
121
|
+
Use 'orch test' to validate + run test suite (fixtures, unit tests, etc).
|
|
122
|
+
|
|
123
|
+
Options:
|
|
124
|
+
--json Output as JSON (for CI/CD pipelines)
|
|
125
|
+
--server Also validate against server (requires auth)
|
|
126
|
+
--profile <name> Use API key from named profile
|
|
127
|
+
--url <url> Agent URL (for code-based agents)
|
|
128
|
+
--docker Validate with Dockerfile
|
|
129
|
+
`)
|
|
119
130
|
.option('--profile <name>', 'Use API key from named profile')
|
|
120
131
|
.option('--json', 'Output as JSON (for CI/CD)')
|
|
121
132
|
.option('--server', 'Also run server-side validation (requires auth)')
|
|
@@ -172,6 +172,10 @@ function printHumanOutput(results, summary, verbose) {
|
|
|
172
172
|
summaryParts.push(chalk_1.default.red(`${summary.errors} error${summary.errors > 1 ? 's' : ''}`));
|
|
173
173
|
}
|
|
174
174
|
process.stdout.write(`Summary: ${summaryParts.join(', ')}\n`);
|
|
175
|
+
// Hint about verbose flag if not already verbose
|
|
176
|
+
if (!verbose) {
|
|
177
|
+
process.stdout.write(chalk_1.default.dim('\nTip: Run with --verbose for detailed information about each check.\n'));
|
|
178
|
+
}
|
|
175
179
|
}
|
|
176
180
|
/**
|
|
177
181
|
* Format results as JSON output.
|
package/dist/lib/errors.js
CHANGED
|
@@ -53,6 +53,27 @@ class CliError extends Error {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
exports.CliError = CliError;
|
|
56
|
+
function dedupeErrorDetail(message, detail) {
|
|
57
|
+
const normalizedMessage = message.replace(/\r\n/g, '\n').trim();
|
|
58
|
+
const normalizedDetail = detail.replace(/\r\n/g, '\n').trim();
|
|
59
|
+
if (!normalizedDetail)
|
|
60
|
+
return null;
|
|
61
|
+
if (!normalizedMessage)
|
|
62
|
+
return normalizedDetail;
|
|
63
|
+
// Some responses repeat the same text in both `message` and `detail`.
|
|
64
|
+
if (normalizedDetail === normalizedMessage)
|
|
65
|
+
return null;
|
|
66
|
+
// If detail starts with the same first line, keep only the additional context.
|
|
67
|
+
const detailLines = normalizedDetail.split('\n');
|
|
68
|
+
if (detailLines[0] === normalizedMessage) {
|
|
69
|
+
const remainder = detailLines.slice(1).join('\n').trim();
|
|
70
|
+
return remainder || null;
|
|
71
|
+
}
|
|
72
|
+
// No useful extra information.
|
|
73
|
+
if (normalizedMessage.includes(normalizedDetail))
|
|
74
|
+
return null;
|
|
75
|
+
return normalizedDetail;
|
|
76
|
+
}
|
|
56
77
|
function formatError(err) {
|
|
57
78
|
if (err instanceof CliError) {
|
|
58
79
|
return err.message;
|
|
@@ -64,8 +85,11 @@ function formatError(err) {
|
|
|
64
85
|
const code = p.error?.code;
|
|
65
86
|
const detail = p.error?.detail || p.detail;
|
|
66
87
|
let msg = `${anyErr.message} (status ${anyErr.status}${code ? `, ${code}` : ''})`;
|
|
67
|
-
if (detail)
|
|
68
|
-
|
|
88
|
+
if (typeof detail === 'string') {
|
|
89
|
+
const dedupedDetail = dedupeErrorDetail(anyErr.message, detail);
|
|
90
|
+
if (dedupedDetail)
|
|
91
|
+
msg += `\n${dedupedDetail}`;
|
|
92
|
+
}
|
|
69
93
|
return msg;
|
|
70
94
|
}
|
|
71
95
|
return anyErr.message;
|
package/package.json
CHANGED