@orchestraworks/cli 0.3.0 → 0.5.1
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 +23 -1256
- package/dist/cli/core/banner.d.ts +10 -0
- package/dist/cli/core/banner.js +63 -0
- package/dist/cli/core/invocation.js +1 -1
- package/dist/cli/core/package-root.d.ts +3 -2
- package/dist/cli/core/package-root.js +4 -4
- package/dist/cli/core/skill-pointer.d.ts +7 -2
- package/dist/cli/core/skill-pointer.js +13 -3
- package/dist/cli/factory/init.d.ts +1 -1
- package/dist/cli/factory/init.js +41 -13
- package/dist/cli/main.js +5 -3
- package/dist/cli/skill-install-fs.d.ts +0 -2
- package/dist/cli/skill-install-fs.js +0 -2
- package/dist/cli/skill.js +20 -7
- package/docs/REFERENCE.md +1278 -0
- package/node_modules/@orchestraworks/client/package.json +1 -1
- package/package.json +7 -8
- package/skills/orchestra/SKILL.md +151 -110
- package/skills/orchestra-fdk/SKILL.md +327 -120
- package/skills/orchestra-fdk/references/authoring.md +26 -15
- package/skills/orchestra-fdk/references/environment.md +39 -24
- package/templates/bug-fix/tsconfig.json +12 -0
- package/templates/engineering-team/lib/setup.sh +16 -0
- package/templates/engineering-team/package.json +1 -1
- package/templates/todo-factory/package.json +1 -1
- package/node_modules/@orchestraworks/workflow-render/LICENSE +0 -202
- package/node_modules/@orchestraworks/workflow-render/dist/constants.d.ts +0 -98
- package/node_modules/@orchestraworks/workflow-render/dist/context.d.ts +0 -37
- package/node_modules/@orchestraworks/workflow-render/dist/geometry-core.d.ts +0 -40
- package/node_modules/@orchestraworks/workflow-render/dist/graph-digest.d.ts +0 -4
- package/node_modules/@orchestraworks/workflow-render/dist/grid.d.ts +0 -33
- package/node_modules/@orchestraworks/workflow-render/dist/heuristic.d.ts +0 -7
- package/node_modules/@orchestraworks/workflow-render/dist/identity.d.ts +0 -9
- package/node_modules/@orchestraworks/workflow-render/dist/index.d.ts +0 -24
- package/node_modules/@orchestraworks/workflow-render/dist/index.js +0 -1
- package/node_modules/@orchestraworks/workflow-render/dist/layout.d.ts +0 -6
- package/node_modules/@orchestraworks/workflow-render/dist/normalize.d.ts +0 -33
- package/node_modules/@orchestraworks/workflow-render/dist/place.d.ts +0 -81
- package/node_modules/@orchestraworks/workflow-render/dist/ports.d.ts +0 -15
- package/node_modules/@orchestraworks/workflow-render/dist/presence.d.ts +0 -6
- package/node_modules/@orchestraworks/workflow-render/dist/route-cells.d.ts +0 -15
- package/node_modules/@orchestraworks/workflow-render/dist/sha256.d.ts +0 -3
- package/node_modules/@orchestraworks/workflow-render/dist/svg.d.ts +0 -7
- package/node_modules/@orchestraworks/workflow-render/dist/text.d.ts +0 -14
- package/node_modules/@orchestraworks/workflow-render/dist/types.d.ts +0 -310
- package/node_modules/@orchestraworks/workflow-render/dist/verify.d.ts +0 -26
- package/node_modules/@orchestraworks/workflow-render/package.json +0 -42
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface BannerTerminal {
|
|
2
|
+
readonly isTTY?: boolean;
|
|
3
|
+
readonly columns?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* The banner and a blank line, or the empty string. `NO_COLOR` follows no-color.org: any non-empty value
|
|
7
|
+
* turns it off. `COLORTERM` of `truecolor` or `24bit` selects 24-bit color. A terminal narrower than the
|
|
8
|
+
* mark and the wordmark gets the mark with the plain word beside its centre.
|
|
9
|
+
*/
|
|
10
|
+
export declare function helpBanner(terminal: BannerTerminal, env: NodeJS.ProcessEnv): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// The Orchestra mark at the top of bare `orchestra` and `orchestra --help`, for a person at a terminal.
|
|
2
|
+
// A coding agent usually reads piped output, so the banner prints only to a terminal with color allowed:
|
|
3
|
+
// piped output stays plain text with no escape codes.
|
|
4
|
+
/**
|
|
5
|
+
* The console's 48-dot mark (`console/src/components/BrandMark.tsx`), its circles rasterized onto a
|
|
6
|
+
* 42-by-40 dot grid and written as braille, eight dots to a character.
|
|
7
|
+
*/
|
|
8
|
+
const MARK = [
|
|
9
|
+
"⠀⠀⠀⠀⢀⣤⡀⠀⠀⣴⣶⡄⣼⣿⡆⠀⠀⠀⠀⠀⠀",
|
|
10
|
+
"⠀⠀⠀⠀⠹⠿⠧⣿⡆⠈⠉⣶⡌⢹⣷⡄⢀⣀⠀⠀⠀",
|
|
11
|
+
"⢠⣶⣄⣿⡷⢰⣶⠀⠀⠿⠀⢈⡀⢀⣭⠀⢿⡿⠂⠀⠀",
|
|
12
|
+
"⠘⠛⢋⣤⡀⢠⡄⠀⠶⠀⠀⠈⠁⠈⠉⠀⣿⠆⣾⣿⡇",
|
|
13
|
+
"⢰⣿⡯⠙⣁⠀⠀⣤⠀⠀⠉⠀⠙⠀⠰⠆⠀⢴⣿⡍⠀",
|
|
14
|
+
"⠀⠉⣤⣌⠛⠀⣤⠀⠀⠒⠀⣀⠀⢴⠄⠐⠿⠂⣩⣄⠀",
|
|
15
|
+
"⠀⣤⣿⠋⣤⡄⢀⡀⠀⠦⠀⢀⠀⢀⣤⠀⣶⣮⠿⠿⠀",
|
|
16
|
+
"⠀⠻⠟⢁⣭⡅⠈⡋⠀⣀⡀⠛⢀⣤⡁⣠⣍⠁⠀⠀⠀",
|
|
17
|
+
"⠀⠀⠀⠘⣻⣧⡸⠿⣂⡙⢣⣤⡄⣙⡁⠻⠟⠀⠀⠀⠀",
|
|
18
|
+
"⠀⠀⠀⠀⠻⠿⠇⠸⣿⠗⠈⠛⠹⣿⡿⠀⠀⠀⠀⠀⠀",
|
|
19
|
+
];
|
|
20
|
+
/** "ORCHESTRA" in the figlet ANSI Shadow face. */
|
|
21
|
+
const WORDMARK = [
|
|
22
|
+
" ██████╗ ██████╗ ██████╗██╗ ██╗███████╗███████╗████████╗██████╗ █████╗ ",
|
|
23
|
+
"██╔═══██╗██╔══██╗██╔════╝██║ ██║██╔════╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗",
|
|
24
|
+
"██║ ██║██████╔╝██║ ███████║█████╗ ███████╗ ██║ ██████╔╝███████║",
|
|
25
|
+
"██║ ██║██╔══██╗██║ ██╔══██║██╔══╝ ╚════██║ ██║ ██╔══██╗██╔══██║",
|
|
26
|
+
"╚██████╔╝██║ ██║╚██████╗██║ ██║███████╗███████║ ██║ ██║ ██║██║ ██║",
|
|
27
|
+
" ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝",
|
|
28
|
+
];
|
|
29
|
+
const MARK_WIDTH = MARK[0].length;
|
|
30
|
+
const GAP = " ";
|
|
31
|
+
/** The mark and the wordmark side by side. */
|
|
32
|
+
const BANNER_WIDTH = MARK_WIDTH + GAP.length + WORDMARK[0].length;
|
|
33
|
+
/** The first mark row the wordmark sits on, which centres it on the mark. */
|
|
34
|
+
const WORDMARK_TOP = Math.floor((MARK.length - WORDMARK.length) / 2);
|
|
35
|
+
const WORD = "Orchestra";
|
|
36
|
+
/** The console's light-theme accent, `consolePalette.json` `light.accent`, as 24-bit RGB. */
|
|
37
|
+
const ACCENT_TRUE_COLOR = "\u001b[38;2;66;99;235m";
|
|
38
|
+
/** The nearest color in the 256-color table, for a terminal that does not announce 24-bit color. */
|
|
39
|
+
const ACCENT_256 = "\u001b[38;5;63m";
|
|
40
|
+
const BOLD = "\u001b[1m";
|
|
41
|
+
const RESET = "\u001b[0m";
|
|
42
|
+
/**
|
|
43
|
+
* The banner and a blank line, or the empty string. `NO_COLOR` follows no-color.org: any non-empty value
|
|
44
|
+
* turns it off. `COLORTERM` of `truecolor` or `24bit` selects 24-bit color. A terminal narrower than the
|
|
45
|
+
* mark and the wordmark gets the mark with the plain word beside its centre.
|
|
46
|
+
*/
|
|
47
|
+
export function helpBanner(terminal, env) {
|
|
48
|
+
if (terminal.isTTY !== true || (env.NO_COLOR ?? "") !== "") {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
const accent = /^(truecolor|24bit)$/i.test(env.COLORTERM ?? "") ? ACCENT_TRUE_COLOR : ACCENT_256;
|
|
52
|
+
const wide = (terminal.columns ?? BANNER_WIDTH) >= BANNER_WIDTH;
|
|
53
|
+
const lines = MARK.map((row, index) => {
|
|
54
|
+
if (wide) {
|
|
55
|
+
const word = WORDMARK[index - WORDMARK_TOP];
|
|
56
|
+
return word === undefined ? `${accent}${row}${RESET}` : `${accent}${row}${GAP}${word.trimEnd()}${RESET}`;
|
|
57
|
+
}
|
|
58
|
+
return index === Math.floor(MARK.length / 2)
|
|
59
|
+
? `${accent}${row}${RESET}${GAP}${BOLD}${WORD}${RESET}`
|
|
60
|
+
: `${accent}${row}${RESET}`;
|
|
61
|
+
});
|
|
62
|
+
return `${lines.join("\n")}\n\n`;
|
|
63
|
+
}
|
|
@@ -14,7 +14,7 @@ import { errorCode } from "./errors.js";
|
|
|
14
14
|
export function addAuthOptions(command) {
|
|
15
15
|
return command
|
|
16
16
|
.option("--profile <name>", "named auth profile to read (default profile if omitted)")
|
|
17
|
-
.option("--base-url <url>", "platform base URL (overrides PLATFORM_BASE_URL and the stored profile)")
|
|
17
|
+
.option("--base-url <url>", "platform base URL (overrides ORCHESTRA_BASE_URL, PLATFORM_BASE_URL and the stored profile)")
|
|
18
18
|
.option("--org <id>", "target organization id (overrides ORCHESTRA_ORGANIZATION_ID and the stored profile)");
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { type KitSkill } from "../skill-install-fs.js";
|
|
1
2
|
/**
|
|
2
3
|
* The package root this process runs from. Under `npx orchestra` outside an installed folder this is npm's
|
|
3
4
|
* disposable cache, which `skills install` refuses to link into.
|
|
4
5
|
*/
|
|
5
6
|
export declare function runningPackageRoot(): string;
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* A bundled skill's path, or `null` when this install does not carry it. The exists-guard is the
|
|
8
9
|
* contract: callers print this as a pointer to follow, and pointing a reader at a path with nothing behind
|
|
9
10
|
* it is a worse repair than printing none. `packageRoot` is the test seam.
|
|
10
11
|
*/
|
|
11
|
-
export declare function resolveBundledSkillPath(packageRoot?: string): string | null;
|
|
12
|
+
export declare function resolveBundledSkillPath(packageRoot?: string, skill?: KitSkill): string | null;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
-
import {
|
|
9
|
+
import { SKILL_FILE_NAME, skillPackageDir } from "../skill-install-fs.js";
|
|
10
10
|
/**
|
|
11
11
|
* The package root this process runs from. Under `npx orchestra` outside an installed folder this is npm's
|
|
12
12
|
* disposable cache, which `skills install` refuses to link into.
|
|
@@ -15,11 +15,11 @@ export function runningPackageRoot() {
|
|
|
15
15
|
return fileURLToPath(new URL("../../..", import.meta.url));
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
18
|
+
* A bundled skill's path, or `null` when this install does not carry it. The exists-guard is the
|
|
19
19
|
* contract: callers print this as a pointer to follow, and pointing a reader at a path with nothing behind
|
|
20
20
|
* it is a worse repair than printing none. `packageRoot` is the test seam.
|
|
21
21
|
*/
|
|
22
|
-
export function resolveBundledSkillPath(packageRoot = runningPackageRoot()) {
|
|
23
|
-
const skillPath = join(packageRoot,
|
|
22
|
+
export function resolveBundledSkillPath(packageRoot = runningPackageRoot(), skill = "orchestra") {
|
|
23
|
+
const skillPath = join(packageRoot, skillPackageDir(skill), SKILL_FILE_NAME);
|
|
24
24
|
return existsSync(skillPath) ? skillPath : null;
|
|
25
25
|
}
|
|
@@ -5,10 +5,15 @@ export interface SkillPointer {
|
|
|
5
5
|
readonly fields: {
|
|
6
6
|
readonly skillPath: string;
|
|
7
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* The lines `orchestra` and `orchestra --help` end with. A coding agent often runs the help right after the
|
|
10
|
+
* install, and npm hides install-script output, so this tail is where the agent learns which skill to read.
|
|
11
|
+
*/
|
|
12
|
+
readonly helpEpilogue: readonly string[];
|
|
8
13
|
}
|
|
9
14
|
/**
|
|
10
15
|
* `null` when this install carries no bundled manual, in which case every surface prints nothing at all.
|
|
11
|
-
* Omission is the contract, not a degraded mode. `skillPath`
|
|
16
|
+
* Omission is the contract, not a degraded mode. `skillPath` and `setupSkillPath` are the test seams.
|
|
12
17
|
*/
|
|
13
|
-
export declare function bundledSkillPointer(skillPath?: string | null): SkillPointer | null;
|
|
18
|
+
export declare function bundledSkillPointer(skillPath?: string | null, setupSkillPath?: string | null): SkillPointer | null;
|
|
14
19
|
export declare function emitSkillPointer(io: CliIo, pointer?: SkillPointer | null): void;
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { resolveBundledSkillPath } from "./package-root.js";
|
|
2
2
|
/**
|
|
3
3
|
* `null` when this install carries no bundled manual, in which case every surface prints nothing at all.
|
|
4
|
-
* Omission is the contract, not a degraded mode. `skillPath`
|
|
4
|
+
* Omission is the contract, not a degraded mode. `skillPath` and `setupSkillPath` are the test seams.
|
|
5
5
|
*/
|
|
6
|
-
export function bundledSkillPointer(skillPath = resolveBundledSkillPath()) {
|
|
6
|
+
export function bundledSkillPointer(skillPath = resolveBundledSkillPath(), setupSkillPath = resolveBundledSkillPath(undefined, "orchestra-fdk")) {
|
|
7
7
|
if (skillPath === null) {
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
10
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
line: `Learn orchestra: read ${skillPath}`,
|
|
12
|
+
fields: { skillPath },
|
|
13
|
+
helpEpilogue: [
|
|
14
|
+
"Coding agents: read both skills before you start.",
|
|
15
|
+
` To learn how to use the orchestra CLI, read ${skillPath}`,
|
|
16
|
+
...(setupSkillPath === null ? [] : [` To set up a factory, read ${setupSkillPath}`]),
|
|
17
|
+
" To link both skills into your harness, run:",
|
|
18
|
+
" orchestra skills install --harness claude|codex --scope project",
|
|
19
|
+
],
|
|
20
|
+
};
|
|
11
21
|
}
|
|
12
22
|
export function emitSkillPointer(io, pointer = bundledSkillPointer()) {
|
|
13
23
|
if (pointer !== null) {
|
|
@@ -13,7 +13,7 @@ type ModelKeyChoice = Exclude<TemplateChoice, "credits">;
|
|
|
13
13
|
/** What `--model-key` takes. */
|
|
14
14
|
export declare const MODEL_KEY_CHOICES: readonly ModelKeyChoice[];
|
|
15
15
|
/** Every file the starter writes besides package.json, in the order it prints them. */
|
|
16
|
-
export declare const STARTER_FILES: readonly ["fdk.json", "main.ts", "image/Dockerfile", "image/setup.sh", "agents/bugfix.md", "skills/codebase/SKILL.md"];
|
|
16
|
+
export declare const STARTER_FILES: readonly ["fdk.json", "tsconfig.json", "main.ts", "image/Dockerfile", "image/setup.sh", "agents/bugfix.md", "skills/codebase/SKILL.md"];
|
|
17
17
|
/** The starter plain `orchestra fdk init` writes, and the only one `--repository` and `--model-key` fill in. */
|
|
18
18
|
export declare const DEFAULT_TEMPLATE = "bug-fix";
|
|
19
19
|
/** The starters `--template` offers by name. Each is a folder under templates/ that brings its own package.json. */
|
package/dist/cli/factory/init.js
CHANGED
|
@@ -13,6 +13,7 @@ export const MODEL_KEY_CHOICES = TEMPLATE_CHOICES.filter((choice) => choice !==
|
|
|
13
13
|
/** Every file the starter writes besides package.json, in the order it prints them. */
|
|
14
14
|
export const STARTER_FILES = [
|
|
15
15
|
"fdk.json",
|
|
16
|
+
"tsconfig.json",
|
|
16
17
|
"main.ts",
|
|
17
18
|
"image/Dockerfile",
|
|
18
19
|
"image/setup.sh",
|
|
@@ -27,8 +28,12 @@ const TEMPLATES_DIR = "templates";
|
|
|
27
28
|
const TEMPLATE_DIR = join(TEMPLATES_DIR, DEFAULT_TEMPLATE);
|
|
28
29
|
/** The one template file `init` fills in; every other file is copied as it is. */
|
|
29
30
|
const FILLED_FILE = "main.ts";
|
|
31
|
+
/** What runs main.ts and checks its types; each is added at the range the kit itself is built with. */
|
|
32
|
+
const STARTER_DEV_PACKAGES = ["tsx", "typescript", "@types/node"];
|
|
33
|
+
/** The script the starter adds when package.json has none of that name. */
|
|
34
|
+
const TYPECHECK_SCRIPT = { name: "typecheck", command: "tsc -p tsconfig.json" };
|
|
30
35
|
/** Any other package in package.json means the folder is a codebase rather than a new folder beside one. */
|
|
31
|
-
const PACKAGES_ALLOWED = new Set([KIT_PACKAGE_NAME,
|
|
36
|
+
const PACKAGES_ALLOWED = new Set([KIT_PACKAGE_NAME, ...STARTER_DEV_PACKAGES]);
|
|
32
37
|
const DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"];
|
|
33
38
|
/** Leaves room for the longest construct path the template declares, so every derived slug stays within 60. */
|
|
34
39
|
const FACTORY_NAME_MAX = 30;
|
|
@@ -90,15 +95,24 @@ export async function initCommand(options, io = defaultIo) {
|
|
|
90
95
|
};
|
|
91
96
|
});
|
|
92
97
|
const record = manifest.kind === "object" ? manifest.record : {};
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
98
|
+
const added = STARTER_DEV_PACKAGES.filter((name) => !listsPackage(record, name));
|
|
99
|
+
const ranges = kitDevRanges(packageRoot, added);
|
|
100
|
+
const devDependencies = { ...dependencyMap(record, "devDependencies"), ...ranges };
|
|
101
|
+
// A `scripts` that is not an object is left as it is rather than replaced.
|
|
102
|
+
const addsTypecheck = record.scripts === undefined || (isRecord(record.scripts) && record.scripts[TYPECHECK_SCRIPT.name] === undefined);
|
|
103
|
+
const scripts = dependencyMap(record, "scripts");
|
|
104
|
+
const merged = {
|
|
105
|
+
...record,
|
|
106
|
+
type: "module",
|
|
107
|
+
...(addsTypecheck ? { scripts: { ...scripts, [TYPECHECK_SCRIPT.name]: TYPECHECK_SCRIPT.command } } : {}),
|
|
108
|
+
...(added.length > 0 ? { devDependencies } : {}),
|
|
109
|
+
};
|
|
96
110
|
writeFileSync(manifestPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
97
111
|
for (const { path, text } of contents) {
|
|
98
112
|
mkdirSync(dirname(join(dir, path)), { recursive: true });
|
|
99
113
|
writeFileSync(join(dir, path), text);
|
|
100
114
|
}
|
|
101
|
-
printNextSteps(io, { repository: repository.full, factoryName, choice, dir,
|
|
115
|
+
printNextSteps(io, { repository: repository.full, factoryName, choice, dir, added, addsTypecheck });
|
|
102
116
|
}
|
|
103
117
|
/** The starters this kit carries: one folder each under templates/. */
|
|
104
118
|
export function availableTemplates(packageRoot = runningPackageRoot()) {
|
|
@@ -325,14 +339,19 @@ function dependencyMap(record, field) {
|
|
|
325
339
|
const value = record[field];
|
|
326
340
|
return isRecord(value) ? value : {};
|
|
327
341
|
}
|
|
328
|
-
/** The
|
|
329
|
-
function
|
|
342
|
+
/** The ranges the kit itself is built and tested with, so the starter runs and type-checks on what the kit was proven on. */
|
|
343
|
+
function kitDevRanges(packageRoot, names) {
|
|
330
344
|
const manifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
345
|
+
const kitDev = isRecord(manifest) ? dependencyMap(manifest, "devDependencies") : {};
|
|
346
|
+
const ranges = {};
|
|
347
|
+
for (const name of names) {
|
|
348
|
+
const range = kitDev[name];
|
|
349
|
+
if (typeof range !== "string") {
|
|
350
|
+
throw new Error(`this orchestra install names no ${name} version to add. Reinstall the kit, then run orchestra fdk init again.`);
|
|
351
|
+
}
|
|
352
|
+
ranges[name] = range;
|
|
334
353
|
}
|
|
335
|
-
return
|
|
354
|
+
return ranges;
|
|
336
355
|
}
|
|
337
356
|
function isRecord(value) {
|
|
338
357
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -341,12 +360,17 @@ function printNextSteps(io, written) {
|
|
|
341
360
|
const inputs = [...SHELL_INPUTS, ...(written.choice === "credits" ? [] : [MODEL_KEY_INPUTS[written.choice]])];
|
|
342
361
|
const width = Math.max(...inputs.map(([name]) => name.length)) + 2;
|
|
343
362
|
io.out(`Wrote a bug-fix factory named '${written.factoryName}' for ${written.repository} into ${written.dir}:`);
|
|
344
|
-
|
|
363
|
+
const changes = [
|
|
364
|
+
`set "type": "module"`,
|
|
365
|
+
...(written.addsTypecheck ? [`added the ${TYPECHECK_SCRIPT.name} script`] : []),
|
|
366
|
+
...(written.added.length > 0 ? [`added ${listed(written.added)} to devDependencies`] : []),
|
|
367
|
+
];
|
|
368
|
+
io.out(` package.json (${changes.join(", ")})`);
|
|
345
369
|
for (const path of STARTER_FILES)
|
|
346
370
|
io.out(` ${path}`);
|
|
347
371
|
io.out("");
|
|
348
372
|
io.out("Next, in this folder:");
|
|
349
|
-
io.out(" 1. npm install");
|
|
373
|
+
io.out(" 1. npm install, then npm run typecheck");
|
|
350
374
|
io.out(" 2. In the shell that runs orchestra, set these values. The platform's team hands them over until image");
|
|
351
375
|
io.out(" hosting exists:");
|
|
352
376
|
for (const [name, what] of inputs)
|
|
@@ -356,3 +380,7 @@ function printNextSteps(io, written) {
|
|
|
356
380
|
io.out(" 3. npx orchestra fdk diff");
|
|
357
381
|
io.out(" 4. npx orchestra fdk apply, then open the GitHub App install link it prints. It waits for the install.");
|
|
358
382
|
}
|
|
383
|
+
/** `a`, `a and b`, or `a, b and c`. */
|
|
384
|
+
function listed(names) {
|
|
385
|
+
return names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
386
|
+
}
|
package/dist/cli/main.js
CHANGED
|
@@ -37,6 +37,7 @@ import { DEFAULT_TEMPLATE, initCommand, MODEL_KEY_CHOICES, OFFERED_TEMPLATES } f
|
|
|
37
37
|
import { grantCommand } from "./api/grant.js";
|
|
38
38
|
import { addAuthOptions } from "./core/invocation.js";
|
|
39
39
|
import { errorDocument, reportError, settleExitCode } from "./core/errors.js";
|
|
40
|
+
import { helpBanner } from "./core/banner.js";
|
|
40
41
|
import { bundledSkillPointer, emitSkillPointer } from "./core/skill-pointer.js";
|
|
41
42
|
import { outputModeFromArgv } from "./core/output-mode.js";
|
|
42
43
|
import { defaultIo, writeLines } from "./core/output.js";
|
|
@@ -170,7 +171,7 @@ LEARN MORE
|
|
|
170
171
|
* entirely. `pointer` is the test seam.
|
|
171
172
|
*/
|
|
172
173
|
export function topLevelHelpText(pointer = bundledSkillPointer()) {
|
|
173
|
-
return pointer === null ? TOP_LEVEL_HELP : `${TOP_LEVEL_HELP}\n${pointer.
|
|
174
|
+
return pointer === null ? TOP_LEVEL_HELP : `${TOP_LEVEL_HELP}\n${pointer.helpEpilogue.join("\n")}\n`;
|
|
174
175
|
}
|
|
175
176
|
const REQUIRE_APPROVAL_HELP = requireApprovalHelp(IMPERATIVE_APPROVAL_DEFAULT);
|
|
176
177
|
/**
|
|
@@ -294,9 +295,10 @@ export function buildProgram(handlers = defaultHandlers, pointer = bundledSkillP
|
|
|
294
295
|
// Deliberately an own-property assignment and NOT `configureHelp`: commander copies `_helpConfiguration`
|
|
295
296
|
// into every subcommand created afterwards, so configuring the formatter here would make every
|
|
296
297
|
// `--help` print the top-level text too. The text is computed once and the closure returns that same
|
|
297
|
-
// string every time, so `--help` printed twice in one process cannot disagree with itself.
|
|
298
|
+
// string every time, so `--help` printed twice in one process cannot disagree with itself. The banner
|
|
299
|
+
// above it depends on the stream the help goes to: stderr for a bare `orchestra`, stdout for `--help`.
|
|
298
300
|
const helpText = topLevelHelpText(pointer);
|
|
299
|
-
program.helpInformation = () => helpText
|
|
301
|
+
program.helpInformation = (context) => `${helpBanner(context?.error === true ? process.stderr : process.stdout, process.env)}${helpText}`;
|
|
300
302
|
// Registered on the program, which is where commander looks: it walks a command's ancestors when an action
|
|
301
303
|
// fires, so one hook here covers the whole tree. It runs after parsing, so `--help` and a usage error —
|
|
302
304
|
// both of which end during the parse — never reach it.
|
|
@@ -10,8 +10,6 @@ export declare const KIT_INSTALL_SPEC = "@orchestraworks/cli";
|
|
|
10
10
|
export declare const KIT_SKILLS: readonly ["orchestra", "orchestra-fdk"];
|
|
11
11
|
export type KitSkill = (typeof KIT_SKILLS)[number];
|
|
12
12
|
export declare function skillPackageDir(skill: KitSkill): string;
|
|
13
|
-
/** The reference skill's directory, which the CLI's own pointer names. */
|
|
14
|
-
export declare const SKILL_DIR_IN_PACKAGE: string;
|
|
15
13
|
export declare const SKILL_FILE_NAME = "SKILL.md";
|
|
16
14
|
/** "other" is a printed-path branch, never a link target. */
|
|
17
15
|
export type LinkableHarness = "claude" | "codex";
|
|
@@ -16,8 +16,6 @@ export const KIT_SKILLS = ["orchestra", "orchestra-fdk"];
|
|
|
16
16
|
export function skillPackageDir(skill) {
|
|
17
17
|
return join("skills", skill);
|
|
18
18
|
}
|
|
19
|
-
/** The reference skill's directory, which the CLI's own pointer names. */
|
|
20
|
-
export const SKILL_DIR_IN_PACKAGE = skillPackageDir("orchestra");
|
|
21
19
|
export const SKILL_FILE_NAME = "SKILL.md";
|
|
22
20
|
/**
|
|
23
21
|
* Identical under a project root and under `$HOME`. The ONE home for these strings: the link math, the menu
|
package/dist/cli/skill.js
CHANGED
|
@@ -21,8 +21,17 @@ const LINKABLE_HARNESSES = ["claude", "codex"];
|
|
|
21
21
|
const HARNESS_LABEL = { claude: "Claude Code", codex: "Codex" };
|
|
22
22
|
const HARNESS_VALUES = ["claude", "codex", "both", "other"];
|
|
23
23
|
const SCOPE_VALUES = ["user", "project"];
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* How a person keeps the linked skills current: the install the links point into is updated in place, so the
|
|
26
|
+
* command matches where the running package sits. A checkout outside both reads as global, the common case.
|
|
27
|
+
*/
|
|
28
|
+
function updateCommand(running) {
|
|
29
|
+
return running.kind === "project" ? `npm update ${KIT_INSTALL_SPEC}` : `npm update -g ${KIT_INSTALL_SPEC}`;
|
|
30
|
+
}
|
|
31
|
+
/** The folder whose `node_modules` holds a project install: `<folder>/node_modules/@orchestraworks/cli`. */
|
|
32
|
+
function installFolder(packageRoot) {
|
|
33
|
+
return dirname(dirname(dirname(packageRoot)));
|
|
34
|
+
}
|
|
26
35
|
export async function skillInstallCommand(options, deps = {}) {
|
|
27
36
|
const io = deps.io ?? defaultIo;
|
|
28
37
|
const env = deps.env ?? process.env;
|
|
@@ -90,14 +99,18 @@ export async function skillInstallCommand(options, deps = {}) {
|
|
|
90
99
|
return;
|
|
91
100
|
}
|
|
92
101
|
io.out("");
|
|
93
|
-
printSkillFiles(io, running
|
|
102
|
+
printSkillFiles(io, running);
|
|
94
103
|
}
|
|
95
104
|
/** The step after the links: a harness loads a new skill only at its next start, so read them now. */
|
|
96
|
-
function printSkillFiles(io,
|
|
105
|
+
function printSkillFiles(io, running) {
|
|
97
106
|
io.out("Read each skill now; your harness loads new skills only at its next start:");
|
|
98
107
|
for (const skill of KIT_SKILLS)
|
|
99
|
-
io.out(` ${skillFileIn(packageRoot, skill)}`);
|
|
100
|
-
io.out(
|
|
108
|
+
io.out(` ${skillFileIn(running.packageRoot, skill)}`);
|
|
109
|
+
io.out("The kit may be installed globally or in the factory folder.");
|
|
110
|
+
io.out(running.kind === "project"
|
|
111
|
+
? `This one is installed in ${installFolder(running.packageRoot)}, so \`${updateCommand(running)}\`, run in ` +
|
|
112
|
+
`that folder, keeps them current.`
|
|
113
|
+
: `This one is installed globally, so \`${updateCommand(running)}\` keeps them current.`);
|
|
101
114
|
}
|
|
102
115
|
function parseChoice(raw, allowed, flag) {
|
|
103
116
|
if (raw === undefined) {
|
|
@@ -194,7 +207,7 @@ function printPointingInstructions(io, running, anchors, fs) {
|
|
|
194
207
|
for (const file of skillFiles)
|
|
195
208
|
io.out(` ${file}`);
|
|
196
209
|
io.out("");
|
|
197
|
-
io.out(`Point your harness at each directory (a link keeps it current across '${
|
|
210
|
+
io.out(`Point your harness at each directory (a link keeps it current across '${updateCommand(running)}'):`);
|
|
198
211
|
for (const harness of LINKABLE_HARNESSES) {
|
|
199
212
|
// The project leg prints the RELATIVE target the command itself would write, so a hand-made link
|
|
200
213
|
// travels with the repo exactly like an installed one. Nothing recomputes after this line — the
|