@orchagent/cli 0.3.95 → 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 +12 -3
- 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/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
|
@@ -1915,7 +1915,8 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1915
1915
|
}
|
|
1916
1916
|
}
|
|
1917
1917
|
// --estimate: show cost estimate before running and ask for confirmation
|
|
1918
|
-
|
|
1918
|
+
// --estimate-only: show cost estimate and exit without running
|
|
1919
|
+
if (options.estimate || options.estimateOnly) {
|
|
1919
1920
|
try {
|
|
1920
1921
|
const est = await (0, api_1.getAgentCostEstimate)(resolved, org, parsed.agent, parsed.version);
|
|
1921
1922
|
const e = est.estimate;
|
|
@@ -1941,7 +1942,11 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1941
1942
|
}
|
|
1942
1943
|
process.stderr.write('\n');
|
|
1943
1944
|
}
|
|
1944
|
-
//
|
|
1945
|
+
// If --estimate-only, exit after showing estimate
|
|
1946
|
+
if (options.estimateOnly) {
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
// Otherwise, ask for confirmation (--estimate only)
|
|
1945
1950
|
const rl = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
1946
1951
|
const iface = rl.createInterface({ input: process.stdin, output: process.stderr });
|
|
1947
1952
|
const answer = await new Promise(resolve => {
|
|
@@ -1954,7 +1959,10 @@ async function executeCloud(agentRef, file, options) {
|
|
|
1954
1959
|
}
|
|
1955
1960
|
}
|
|
1956
1961
|
catch {
|
|
1957
|
-
// 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
|
+
}
|
|
1958
1966
|
process.stderr.write(chalk_1.default.gray('Could not fetch cost estimate. Proceeding...\n\n'));
|
|
1959
1967
|
}
|
|
1960
1968
|
}
|
|
@@ -2879,6 +2887,7 @@ function registerRunCommand(program) {
|
|
|
2879
2887
|
.option('--json', 'Output raw JSON')
|
|
2880
2888
|
.option('--verbose', 'Show sandbox stdout/stderr and debug info (cloud only)')
|
|
2881
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)')
|
|
2882
2891
|
.option('--provider <provider>', 'LLM provider (openai, anthropic, gemini, ollama)')
|
|
2883
2892
|
.option('--model <model>', 'LLM model to use (overrides agent default)')
|
|
2884
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)')
|
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