ghostrail 0.4.0 → 0.6.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/README.md +160 -2
- package/dist/cli/commands.d.ts +42 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +150 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/prompt.d.ts +26 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +48 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +13 -0
- package/dist/commands/help.js.map +1 -1
- package/dist/factory/build.d.ts +2 -7
- package/dist/factory/build.d.ts.map +1 -1
- package/dist/factory/build.js +2 -16
- package/dist/factory/build.js.map +1 -1
- package/dist/image/build.d.ts +28 -0
- package/dist/image/build.d.ts.map +1 -0
- package/dist/image/build.js +40 -0
- package/dist/image/build.js.map +1 -0
- package/dist/secrets/editor.d.ts +16 -0
- package/dist/secrets/editor.d.ts.map +1 -0
- package/dist/secrets/editor.js +35 -0
- package/dist/secrets/editor.js.map +1 -0
- package/dist/secrets/env-file.d.ts +0 -13
- package/dist/secrets/env-file.d.ts.map +1 -1
- package/dist/secrets/env-file.js +6 -2
- package/dist/secrets/env-file.js.map +1 -1
- package/dist/secrets/index.d.ts +3 -0
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +3 -0
- package/dist/secrets/index.js.map +1 -1
- package/dist/secrets/known.d.ts +19 -0
- package/dist/secrets/known.d.ts.map +1 -0
- package/dist/secrets/known.js +23 -0
- package/dist/secrets/known.js.map +1 -0
- package/dist/secrets/setup.d.ts +44 -0
- package/dist/secrets/setup.d.ts.map +1 -0
- package/dist/secrets/setup.js +124 -0
- package/dist/secrets/setup.js.map +1 -0
- package/package.json +2 -2
- package/scripts/build-factory-image.sh +10 -0
- package/skill/ghostrail/SKILL.md +11 -2
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { parseEnvFile } from "./env-file.js";
|
|
4
|
+
import { DEFAULT_FORWARD_ENV } from "./known.js";
|
|
5
|
+
/** The secrets file holds credentials, so it is created owner-only. */
|
|
6
|
+
export const SECRETS_FILE_MODE = 0o600;
|
|
7
|
+
/** The global config dir, created owner-only for the same reason. */
|
|
8
|
+
export const SECRETS_DIR_MODE = 0o700;
|
|
9
|
+
/**
|
|
10
|
+
* What each credential is for. Keyed by {@link CredentialKey}, so adding a name
|
|
11
|
+
* to `DEFAULT_FORWARD_ENV` without describing it here is a type error rather
|
|
12
|
+
* than a key silently missing from the scaffolded file.
|
|
13
|
+
*/
|
|
14
|
+
const DESCRIPTIONS = {
|
|
15
|
+
ANTHROPIC_API_KEY: "Anthropic API key. Bills metered API usage, and wins over CLAUDE_CODE_OAUTH_TOKEN when both are set.",
|
|
16
|
+
CLAUDE_CODE_OAUTH_TOKEN: "From `claude setup-token`. Bills a Claude Pro/Max subscription; leave ANTHROPIC_API_KEY unset to use it.",
|
|
17
|
+
GH_TOKEN: "GitHub token with repo + pull-request write. Used to push factory branches and open PRs.",
|
|
18
|
+
GITHUB_TOKEN: "Alternative name for the GitHub token, read when GH_TOKEN is unset.",
|
|
19
|
+
LINEAR_API_KEY: "Linear personal API key (Settings > Security & Access). Required for every run.",
|
|
20
|
+
};
|
|
21
|
+
/** Every credential ghostrail reads, in forwarding order. */
|
|
22
|
+
export const KNOWN_SECRETS = DEFAULT_FORWARD_ENV.map((key) => ({
|
|
23
|
+
key,
|
|
24
|
+
description: DESCRIPTIONS[key],
|
|
25
|
+
}));
|
|
26
|
+
/**
|
|
27
|
+
* A commented placeholder line: a comment whose first content is `KEY=`, with an
|
|
28
|
+
* optional `export`. Requiring the `=` is what separates a placeholder from
|
|
29
|
+
* prose that merely names a variable.
|
|
30
|
+
*/
|
|
31
|
+
const COMMENTED_ASSIGNMENT = /^#+\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
32
|
+
/**
|
|
33
|
+
* The variable names the content refers to, whether as a live assignment or as a
|
|
34
|
+
* commented placeholder. Both count, so topping a file up never re-adds a
|
|
35
|
+
* placeholder the file already carries.
|
|
36
|
+
*
|
|
37
|
+
* Only names are returned. No value is read, returned, or surfaced.
|
|
38
|
+
*/
|
|
39
|
+
export function mentionedKeys(content) {
|
|
40
|
+
const keys = new Set(Object.keys(parseEnvFile(content)));
|
|
41
|
+
for (const rawLine of content.split("\n")) {
|
|
42
|
+
const line = rawLine.trim();
|
|
43
|
+
if (!line.startsWith("#"))
|
|
44
|
+
continue;
|
|
45
|
+
const match = COMMENTED_ASSIGNMENT.exec(line);
|
|
46
|
+
const key = match?.[1];
|
|
47
|
+
if (key !== undefined)
|
|
48
|
+
keys.add(key);
|
|
49
|
+
}
|
|
50
|
+
return keys;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The known credentials the content does not mention, in `KNOWN_SECRETS` order.
|
|
54
|
+
* `undefined` content (no file yet) means every one of them is missing. Names
|
|
55
|
+
* the file carries that ghostrail does not read are ignored.
|
|
56
|
+
*/
|
|
57
|
+
export function missingSecrets(content) {
|
|
58
|
+
if (content === undefined)
|
|
59
|
+
return [...KNOWN_SECRETS];
|
|
60
|
+
const mentioned = mentionedKeys(content);
|
|
61
|
+
return KNOWN_SECRETS.filter((spec) => !mentioned.has(spec.key));
|
|
62
|
+
}
|
|
63
|
+
const HEADER = [
|
|
64
|
+
"# ghostrail credentials.",
|
|
65
|
+
"#",
|
|
66
|
+
"# ghostrail reads this file on every run, watch, respond, and triage. A value",
|
|
67
|
+
"# set here overrides the same variable in your environment (precedence: a",
|
|
68
|
+
"# repo's .ghostrail.env, then this file, then the ambient environment).",
|
|
69
|
+
"#",
|
|
70
|
+
"# Uncomment a line and fill in the value. Leave the rest commented: an",
|
|
71
|
+
"# uncommented empty value would blank out a working credential from your",
|
|
72
|
+
"# environment. This file is created mode 600 and belongs to you alone.",
|
|
73
|
+
];
|
|
74
|
+
/**
|
|
75
|
+
* The description + commented placeholder block for one credential. A
|
|
76
|
+
* description carrying newlines becomes one comment line per line, so no input
|
|
77
|
+
* can produce an uncommented line and quietly turn the file into a live
|
|
78
|
+
* assignment.
|
|
79
|
+
*/
|
|
80
|
+
function block(spec) {
|
|
81
|
+
const description = spec.description.split("\n").map((line) => `# ${line}`);
|
|
82
|
+
return ["", ...description, `# ${spec.key}=`];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The full content of a fresh secrets file: a header, then a commented
|
|
86
|
+
* placeholder per spec. Every line is a comment, so creating the file never
|
|
87
|
+
* changes what ghostrail resolves.
|
|
88
|
+
*/
|
|
89
|
+
export function renderFile(specs) {
|
|
90
|
+
const lines = [...HEADER];
|
|
91
|
+
for (const spec of specs)
|
|
92
|
+
lines.push(...block(spec));
|
|
93
|
+
return `${lines.join("\n")}\n`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Append placeholder blocks for `specs` to existing content, preserving every
|
|
97
|
+
* byte of it up to its trailing newlines. Only the run of trailing newlines can
|
|
98
|
+
* change, which keeps repeated runs from stacking blank lines.
|
|
99
|
+
*/
|
|
100
|
+
export function appendPlaceholders(existing, specs) {
|
|
101
|
+
if (specs.length === 0)
|
|
102
|
+
return existing;
|
|
103
|
+
const base = existing.replace(/\n+$/, "");
|
|
104
|
+
const lines = ["# Added by ghostrail secrets-setup."];
|
|
105
|
+
for (const spec of specs)
|
|
106
|
+
lines.push(...block(spec));
|
|
107
|
+
const body = `${lines.join("\n")}\n`;
|
|
108
|
+
return base.trim().length === 0 ? body : `${base}\n\n${body}`;
|
|
109
|
+
}
|
|
110
|
+
/** The secrets file's content, or undefined when it does not exist. */
|
|
111
|
+
export async function readSecretsFile(path) {
|
|
112
|
+
try {
|
|
113
|
+
return await readFile(path, "utf8");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** Write the secrets file owner-only, creating its directory owner-only too. */
|
|
120
|
+
export async function writeSecretsFile(path, content) {
|
|
121
|
+
await mkdir(dirname(path), { recursive: true, mode: SECRETS_DIR_MODE });
|
|
122
|
+
await writeFile(path, content, { encoding: "utf8", mode: SECRETS_FILE_MODE });
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/secrets/setup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghostrail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Open source software factory for coding agents. Config-driven loops, pluggable isolation backends, human merge gate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"bin": {
|
|
25
25
|
"ghostrail": "dist/bin.js"
|
|
26
26
|
},
|
|
27
|
-
"files": ["dist", "templates", "skill", "docker", "LICENSE", "NOTICE", "README.md"],
|
|
27
|
+
"files": ["dist", "templates", "skill", "docker", "scripts", "LICENSE", "NOTICE", "README.md"],
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=22"
|
|
30
30
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build the ghostrail factory toolchain image used by the container backend.
|
|
3
|
+
# Usage: scripts/build-factory-image.sh [image-tag] (default: ghostrail-factory:local)
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
IMAGE="${1:-ghostrail-factory:local}"
|
|
7
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
8
|
+
|
|
9
|
+
docker build -f "$DIR/docker/factory.Dockerfile" -t "$IMAGE" "$DIR/docker"
|
|
10
|
+
echo "built $IMAGE"
|
package/skill/ghostrail/SKILL.md
CHANGED
|
@@ -123,10 +123,19 @@ Diagnose a setup without changing anything. Check and report each, with the fix:
|
|
|
123
123
|
- **Credentials** — report which of `LINEAR_API_KEY`, `GH_TOKEN`/`GITHUB_TOKEN`,
|
|
124
124
|
and `ANTHROPIC_API_KEY` **or** `CLAUDE_CODE_OAUTH_TOKEN` are set. Report only
|
|
125
125
|
set/unset, **never print a value**. Note that `ANTHROPIC_API_KEY` wins over the
|
|
126
|
-
OAuth token, so it must be unset to bill a Pro/Max subscription.
|
|
126
|
+
OAuth token, so it must be unset to bill a Pro/Max subscription. Remember they
|
|
127
|
+
can come from a file as well as the environment: `~/.config/ghostrail/secrets.env`
|
|
128
|
+
and a repo's `.ghostrail.env`, which beat an exported variable of the same name.
|
|
129
|
+
When something is missing, the fix to suggest is `ghostrail secrets-setup --write`,
|
|
130
|
+
which scaffolds that file and opens it in the user's editor. Never offer to fill
|
|
131
|
+
in a value yourself.
|
|
127
132
|
- **GitHub** — `gh auth status` succeeds.
|
|
128
133
|
- **Container backend**, if `[backend].kind = "container"` — the configured
|
|
129
|
-
`image` exists locally (`docker images`). If not,
|
|
134
|
+
`image` exists locally (`docker images`). If not, the fix is
|
|
135
|
+
`ghostrail image build` (add `--tag` when the config names something other
|
|
136
|
+
than `ghostrail-factory:local`). No repo checkout is needed; the Dockerfile
|
|
137
|
+
ships in the package. A `local-worktree` factory needs no image at all, so do
|
|
138
|
+
not report a missing one as a problem.
|
|
130
139
|
|
|
131
140
|
Finish with a short ordered list of what to fix first.
|
|
132
141
|
|