@cybernetyx1/atlasflow-cli 0.1.0 → 0.1.2
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 +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +411 -61
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ The `atlasflow` command-line tool for scaffolding, running, building, testing, a
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
|
|
8
|
+
npm install @cybernetyx1/atlasflow-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Part of the AtlasFlow monorepo. Proprietary.
|
|
@@ -15,9 +15,9 @@ Part of the AtlasFlow monorepo. Proprietary.
|
|
|
15
15
|
Scaffold a workspace and run it locally:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
atlasflow init
|
|
19
|
-
|
|
20
|
-
atlasflow dev --console # watch + serve locally with an operator console
|
|
18
|
+
npx atlasflow init # scaffold a new agent workspace
|
|
19
|
+
npm install
|
|
20
|
+
npx atlasflow dev --console # watch + serve locally with an operator console
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
Commands:
|
package/dist/index.d.ts
CHANGED
|
@@ -12,5 +12,6 @@ declare function defineConfig(config: UserAtlasFlowConfig): UserAtlasFlowConfig;
|
|
|
12
12
|
/** @cybernetyx1/atlasflow-cli — entrypoint and arg parsing. */
|
|
13
13
|
|
|
14
14
|
declare function main(argv: string[]): Promise<void>;
|
|
15
|
+
declare function assertSupportedNode(version: string): void;
|
|
15
16
|
|
|
16
|
-
export { type UserAtlasFlowConfig, defineConfig, main };
|
|
17
|
+
export { type UserAtlasFlowConfig, assertSupportedNode, defineConfig, main };
|
package/dist/index.js
CHANGED
|
@@ -1942,7 +1942,7 @@ async function bundle(opts) {
|
|
|
1942
1942
|
platform: "node",
|
|
1943
1943
|
target: "node22",
|
|
1944
1944
|
packages: "external",
|
|
1945
|
-
sourcemap:
|
|
1945
|
+
sourcemap: false,
|
|
1946
1946
|
logLevel: "silent",
|
|
1947
1947
|
plugins: [skillPlugin(opts.projectRoot ?? opts.resolveDir)],
|
|
1948
1948
|
...opts.cjsPathPolyfill ? { banner: { js: 'var __filename = "/worker.mjs"; var __dirname = "/";' } } : {}
|
|
@@ -3072,7 +3072,11 @@ async function infoCommand(args) {
|
|
|
3072
3072
|
} else {
|
|
3073
3073
|
process.stdout.write(formatAgentFolderCheck(ws.agentFolder, check, { diff: includeDiff }));
|
|
3074
3074
|
}
|
|
3075
|
-
if (!check.ok)
|
|
3075
|
+
if (!check.ok) {
|
|
3076
|
+
throw new Error(
|
|
3077
|
+
`atlasflow info --check failed: ${check.items.filter((item) => item.status !== "current").map((item) => `${item.name} ${item.status}`).join(", ")}. Next: run atlasflow info to regenerate .atlasflow artifacts, then rerun atlasflow info --check.`
|
|
3078
|
+
);
|
|
3079
|
+
}
|
|
3076
3080
|
return;
|
|
3077
3081
|
}
|
|
3078
3082
|
const artifacts = writeAgentFolderArtifacts(ws.agentFolder);
|
|
@@ -3085,6 +3089,82 @@ async function infoCommand(args) {
|
|
|
3085
3089
|
}
|
|
3086
3090
|
requireAgentFolderWorkspace(ws, root);
|
|
3087
3091
|
}
|
|
3092
|
+
async function doctorCommand(args) {
|
|
3093
|
+
const config = await resolveCliConfig(args);
|
|
3094
|
+
const root = config.root;
|
|
3095
|
+
loadEnv(root);
|
|
3096
|
+
const checks = [];
|
|
3097
|
+
const pkg = readProjectPackageJson(root);
|
|
3098
|
+
const packageManager = detectPackageManager(root, pkg.manifest);
|
|
3099
|
+
checks.push({
|
|
3100
|
+
name: "Node.js",
|
|
3101
|
+
ok: nodeMajor(process.versions.node) >= 22,
|
|
3102
|
+
detail: `current ${process.versions.node}`,
|
|
3103
|
+
next: "Install Node.js 22 or newer, then rerun atlasflow doctor."
|
|
3104
|
+
});
|
|
3105
|
+
checks.push({
|
|
3106
|
+
name: "Project package.json",
|
|
3107
|
+
ok: Boolean(pkg.manifest),
|
|
3108
|
+
detail: pkg.error ?? (pkg.manifest ? "found" : "missing"),
|
|
3109
|
+
next: "Run atlasflow init, or run atlasflow doctor from the project root."
|
|
3110
|
+
});
|
|
3111
|
+
checks.push({
|
|
3112
|
+
name: "Package manager",
|
|
3113
|
+
ok: Boolean(packageManager.kind),
|
|
3114
|
+
detail: packageManager.detail,
|
|
3115
|
+
next: "Run npm install to create package-lock.json, or use the package manager already chosen by this project."
|
|
3116
|
+
});
|
|
3117
|
+
const dependencyCheck = checkAtlasFlowDependencies(root, pkg.manifest);
|
|
3118
|
+
checks.push(dependencyCheck);
|
|
3119
|
+
let agent;
|
|
3120
|
+
try {
|
|
3121
|
+
const ws = loadWorkspace(root);
|
|
3122
|
+
agent = ws.agentFolder;
|
|
3123
|
+
checks.push({
|
|
3124
|
+
name: "Agent folder",
|
|
3125
|
+
ok: Boolean(agent),
|
|
3126
|
+
detail: agent ? `found agent/${path5.basename(agent.configFile ?? "agent.jsonc")} (${agent.name})` : "missing agent/agent.jsonc",
|
|
3127
|
+
next: "Run atlasflow init, or add agent/agent.jsonc plus agent/identity/profile.md."
|
|
3128
|
+
});
|
|
3129
|
+
} catch (err) {
|
|
3130
|
+
checks.push({
|
|
3131
|
+
name: "Agent folder",
|
|
3132
|
+
ok: false,
|
|
3133
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
3134
|
+
next: "Fix agent/agent.jsonc and agent/identity/, then rerun atlasflow doctor."
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
if (agent) {
|
|
3138
|
+
const modelKey = modelSecretEnvName(agent.manifest.model);
|
|
3139
|
+
checks.push({
|
|
3140
|
+
name: "Model key",
|
|
3141
|
+
ok: !modelKey || Boolean(process.env[modelKey]),
|
|
3142
|
+
detail: modelKey ? `${modelKey}${process.env[modelKey] ? " is set" : " is not set"}` : `model ${agent.manifest.model} does not require a local provider key`,
|
|
3143
|
+
next: modelKey ? `Set ${modelKey} in .env or the deployment secret store.` : ""
|
|
3144
|
+
});
|
|
3145
|
+
const artifactCheck = checkAgentFolderArtifacts(agent);
|
|
3146
|
+
checks.push({
|
|
3147
|
+
name: "Generated artifacts",
|
|
3148
|
+
ok: artifactCheck.ok,
|
|
3149
|
+
detail: artifactCheck.ok ? ".atlasflow artifacts are current" : artifactCheck.items.filter((item) => item.status !== "current").map((item) => `${item.name} ${item.status}`).join(", "),
|
|
3150
|
+
next: "Run atlasflow info to generate .atlasflow artifacts, then rerun atlasflow doctor."
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
const failed = checks.filter((check) => !check.ok);
|
|
3154
|
+
if (args.json === true) {
|
|
3155
|
+
console.log(JSON.stringify({ ok: failed.length === 0, root, checks }, null, 2));
|
|
3156
|
+
if (failed.length) process.exitCode = 1;
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const lines = [`AtlasFlow doctor (${root})`];
|
|
3160
|
+
for (const check of checks) {
|
|
3161
|
+
lines.push(`${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`);
|
|
3162
|
+
if (!check.ok) lines.push(` Next: ${check.next}`);
|
|
3163
|
+
}
|
|
3164
|
+
lines.push(failed.length ? `Status: ${failed.length} issue${failed.length === 1 ? "" : "s"} found.` : "Status: all checks passed.");
|
|
3165
|
+
console.log(lines.join("\n"));
|
|
3166
|
+
if (failed.length) process.exitCode = 1;
|
|
3167
|
+
}
|
|
3088
3168
|
function agentFolderInfoJson(agent, artifacts, target) {
|
|
3089
3169
|
return {
|
|
3090
3170
|
atlasflow: 1,
|
|
@@ -3195,6 +3275,78 @@ function stableLines(value) {
|
|
|
3195
3275
|
const trimmed = normalized.endsWith("\n") ? normalized.slice(0, -1) : normalized;
|
|
3196
3276
|
return trimmed ? trimmed.split("\n") : [];
|
|
3197
3277
|
}
|
|
3278
|
+
function nodeMajor(version) {
|
|
3279
|
+
const major = Number(version.split(".")[0]);
|
|
3280
|
+
return Number.isFinite(major) ? major : 0;
|
|
3281
|
+
}
|
|
3282
|
+
function readProjectPackageJson(root) {
|
|
3283
|
+
const file = path5.join(root, "package.json");
|
|
3284
|
+
if (!fs5.existsSync(file)) return {};
|
|
3285
|
+
try {
|
|
3286
|
+
const parsed = JSON.parse(fs5.readFileSync(file, "utf8"));
|
|
3287
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { error: "package.json must contain an object" };
|
|
3288
|
+
return { manifest: parsed };
|
|
3289
|
+
} catch (err) {
|
|
3290
|
+
return { error: `invalid package.json (${err instanceof Error ? err.message : String(err)})` };
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
function detectPackageManager(root, manifest) {
|
|
3294
|
+
if (fs5.existsSync(path5.join(root, "pnpm-lock.yaml"))) return { kind: "pnpm", detail: "pnpm-lock.yaml found" };
|
|
3295
|
+
if (fs5.existsSync(path5.join(root, "package-lock.json"))) return { kind: "npm", detail: "package-lock.json found" };
|
|
3296
|
+
if (fs5.existsSync(path5.join(root, "yarn.lock"))) return { kind: "yarn", detail: "yarn.lock found" };
|
|
3297
|
+
const packageManager = typeof manifest?.packageManager === "string" ? manifest.packageManager : void 0;
|
|
3298
|
+
if (packageManager?.startsWith("pnpm@")) return { kind: "pnpm", detail: `packageManager=${packageManager}; lockfile missing` };
|
|
3299
|
+
if (packageManager?.startsWith("npm@")) return { kind: "npm", detail: `packageManager=${packageManager}; lockfile missing` };
|
|
3300
|
+
if (packageManager?.startsWith("yarn@")) return { kind: "yarn", detail: `packageManager=${packageManager}; lockfile missing` };
|
|
3301
|
+
return { detail: "no package lockfile found" };
|
|
3302
|
+
}
|
|
3303
|
+
function checkAtlasFlowDependencies(root, manifest) {
|
|
3304
|
+
if (!manifest) {
|
|
3305
|
+
return {
|
|
3306
|
+
name: "AtlasFlow dependencies",
|
|
3307
|
+
ok: false,
|
|
3308
|
+
detail: "package.json missing",
|
|
3309
|
+
next: "Run atlasflow init."
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
const deps = projectDependencyMap(manifest);
|
|
3313
|
+
const required = [atlasflowPackageName("cli"), atlasflowPackageName("runtime")];
|
|
3314
|
+
const missing = required.filter((name) => !deps.has(name));
|
|
3315
|
+
if (missing.length) {
|
|
3316
|
+
return {
|
|
3317
|
+
name: "AtlasFlow dependencies",
|
|
3318
|
+
ok: false,
|
|
3319
|
+
detail: `missing ${missing.join(", ")}`,
|
|
3320
|
+
next: "Run atlasflow init to merge the missing dependencies into package.json, then run npm install."
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
const unlinked = required.filter((name) => !fs5.existsSync(path5.join(root, "node_modules", ...name.split("/"))));
|
|
3324
|
+
if (unlinked.length) {
|
|
3325
|
+
return {
|
|
3326
|
+
name: "AtlasFlow dependencies",
|
|
3327
|
+
ok: false,
|
|
3328
|
+
detail: `not installed ${unlinked.join(", ")}`,
|
|
3329
|
+
next: "Run npm install, then rerun atlasflow doctor."
|
|
3330
|
+
};
|
|
3331
|
+
}
|
|
3332
|
+
return {
|
|
3333
|
+
name: "AtlasFlow dependencies",
|
|
3334
|
+
ok: true,
|
|
3335
|
+
detail: `${required.join(", ")} installed`,
|
|
3336
|
+
next: ""
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
function projectDependencyMap(manifest) {
|
|
3340
|
+
const out = /* @__PURE__ */ new Map();
|
|
3341
|
+
for (const section of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
|
|
3342
|
+
const deps = manifest[section];
|
|
3343
|
+
if (!deps || typeof deps !== "object" || Array.isArray(deps)) continue;
|
|
3344
|
+
for (const [name, range] of Object.entries(deps)) {
|
|
3345
|
+
if (typeof range === "string") out.set(name, range);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
return out;
|
|
3349
|
+
}
|
|
3198
3350
|
var AGENT_BUNDLE_ROOT = "agent";
|
|
3199
3351
|
var AGENT_BUNDLE_EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([".cache", ".git", ".turbo", ".wrangler", "dist", "node_modules"]);
|
|
3200
3352
|
var AGENT_BUNDLE_EXCLUDED_FILES = /* @__PURE__ */ new Set([".ds_store"]);
|
|
@@ -3443,7 +3595,7 @@ function runProcess(command, args, cwd, label) {
|
|
|
3443
3595
|
child.on("error", (err) => {
|
|
3444
3596
|
const code = err.code;
|
|
3445
3597
|
if (code === "ENOENT") {
|
|
3446
|
-
reject(new Error(`Could not find ${command}. Run
|
|
3598
|
+
reject(new Error(`Could not find ${command}. Run ${suggestedInstallCommand(cwd)}, install Wrangler, or pass --wrangler <path>.`));
|
|
3447
3599
|
return;
|
|
3448
3600
|
}
|
|
3449
3601
|
reject(err);
|
|
@@ -4725,15 +4877,17 @@ async function connectCommand(args) {
|
|
|
4725
4877
|
var DOCS = {
|
|
4726
4878
|
quickstart: `Quickstart
|
|
4727
4879
|
|
|
4728
|
-
atlasflow init
|
|
4729
|
-
|
|
4880
|
+
npm exec --yes --package @cybernetyx1/atlasflow-cli -- atlasflow init
|
|
4881
|
+
npm install
|
|
4730
4882
|
cp .env.example .env
|
|
4731
4883
|
# set ANTHROPIC_API_KEY in .env
|
|
4732
|
-
atlasflow info
|
|
4733
|
-
atlasflow run --message "hi"
|
|
4734
|
-
atlasflow connect
|
|
4735
|
-
atlasflow dev --console
|
|
4736
|
-
curl -X POST localhost:4577/runs/hello -H 'content-type: application/json' -d '{"message":"hi"}'
|
|
4884
|
+
npx atlasflow info
|
|
4885
|
+
npx atlasflow run --message "hi"
|
|
4886
|
+
npx atlasflow connect
|
|
4887
|
+
npx atlasflow dev --console
|
|
4888
|
+
curl -X POST localhost:4577/runs/hello -H 'content-type: application/json' -d '{"message":"hi"}'
|
|
4889
|
+
|
|
4890
|
+
Contributors working inside the monorepo use pnpm. New app projects should not need a monorepo checkout or build.`,
|
|
4737
4891
|
agents: `Authoring agents
|
|
4738
4892
|
|
|
4739
4893
|
The default project has one visible agent/ folder. agent/agent.jsonc stores model
|
|
@@ -4762,6 +4916,29 @@ slot path diagnostics, and a prompt preview. Use atlasflow info --json for the
|
|
|
4762
4916
|
machine-readable compiled graph, atlasflow info --check in CI to fail when
|
|
4763
4917
|
generated artifacts are stale, and atlasflow info --check --diff to print a compact
|
|
4764
4918
|
stale-artifact diff.`,
|
|
4919
|
+
tools: `Custom tools
|
|
4920
|
+
|
|
4921
|
+
Put custom tool modules in agent/tools/*.ts. Each module can export one default
|
|
4922
|
+
tool, an array of tools, { tools }, or named tool exports. Define the argument
|
|
4923
|
+
schema with Valibot so the runtime can validate model-provided inputs before
|
|
4924
|
+
execute runs.
|
|
4925
|
+
|
|
4926
|
+
agent/tools/add.ts
|
|
4927
|
+
|
|
4928
|
+
import { defineTool } from "@cybernetyx1/atlasflow-runtime";
|
|
4929
|
+
import * as v from "valibot";
|
|
4930
|
+
|
|
4931
|
+
export default defineTool({
|
|
4932
|
+
name: "add",
|
|
4933
|
+
description: "Add two numbers and return their sum.",
|
|
4934
|
+
parameters: v.object({ a: v.number(), b: v.number() }),
|
|
4935
|
+
execute: async ({ a, b }) => String(a + b),
|
|
4936
|
+
});
|
|
4937
|
+
|
|
4938
|
+
Keep side effects inside execute and keep credentials outside agent/. For external
|
|
4939
|
+
APIs, prefer a connection module plus an allowlist in agent/agent.jsonc so deploy-time
|
|
4940
|
+
secrets bind outside the agent source folder. Run atlasflow info to confirm the tool
|
|
4941
|
+
was discovered and atlasflow build to prove the module bundles.`,
|
|
4765
4942
|
browser: `Browser integration
|
|
4766
4943
|
|
|
4767
4944
|
Never put ATLASFLOW_API_KEY in browser code. Keep the master key on your server,
|
|
@@ -4863,13 +5040,14 @@ connections fail fast at run time. Never put credentials inside agent/.`,
|
|
|
4863
5040
|
channels: `Channels
|
|
4864
5041
|
|
|
4865
5042
|
Put inbound webhook/session adapters in agent/channels/<name>.ts. Channels are
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
5043
|
+
public ingress routes by design: platform webhooks and app clients call them
|
|
5044
|
+
without the server master key. Every deployed channel must verify its own bearer
|
|
5045
|
+
token, signature, or provider secret before it dispatches to the agent, otherwise
|
|
5046
|
+
anyone can spend your model budget. Tools and connections remain outbound
|
|
5047
|
+
capabilities the model can call. Run atlasflow add channel <name> for a bearer-token
|
|
5048
|
+
HTTP channel, or pass --provider github|slack for signed webhook templates.
|
|
5049
|
+
Generated templates fail closed in deployed mode unless the required token or
|
|
5050
|
+
signing secret is configured; ATLASFLOW_MODE=local keeps local development easy.`,
|
|
4873
5051
|
"rate-limit": `Rate limiting
|
|
4874
5052
|
|
|
4875
5053
|
Built Node and Cloudflare servers enable an in-process rate limit by default for
|
|
@@ -4908,10 +5086,34 @@ Use connectMcpServer() for stdio or HTTP MCP servers. In Worker deployments, ope
|
|
|
4908
5086
|
MCP connections lazily inside a toolSlots factory and return { tools, cleanup } so
|
|
4909
5087
|
connections are created per request and closed after the run settles. Use
|
|
4910
5088
|
atlasflow add mcp <name> to scaffold a lazy connector and bindings stub.`,
|
|
5089
|
+
prompt: `Prompt recipe
|
|
5090
|
+
|
|
5091
|
+
agent/agent.jsonc can include prompt.recipe and prompt.contextBudget. The standard
|
|
5092
|
+
folder scaffold uses "atlas-brain-default", which assembles identity markdown,
|
|
5093
|
+
principles, boundaries, skills, context, memory slots, tools, channels, and other
|
|
5094
|
+
folder-discovered loadout into the compiled system prompt. Use contextBudget to
|
|
5095
|
+
cap how much discovered context can be included eagerly. Run atlasflow info to
|
|
5096
|
+
inspect .atlasflow/compiled-system-prompt.md and use atlasflow info --check in CI
|
|
5097
|
+
to catch stale prompt artifacts.`,
|
|
4911
5098
|
"structured-output": `Structured output
|
|
4912
5099
|
|
|
4913
|
-
Declare a Valibot result schema in agent/agent.ts to validate model output.
|
|
4914
|
-
|
|
5100
|
+
Declare a Valibot result schema in agent/agent.ts to validate model output.
|
|
5101
|
+
|
|
5102
|
+
agent/agent.ts
|
|
5103
|
+
|
|
5104
|
+
import { defineAgent } from "@cybernetyx1/atlasflow-runtime";
|
|
5105
|
+
import * as v from "valibot";
|
|
5106
|
+
|
|
5107
|
+
const Result = v.object({
|
|
5108
|
+
status: v.picklist(["ok", "needs-info"]),
|
|
5109
|
+
answer: v.string(),
|
|
5110
|
+
});
|
|
5111
|
+
|
|
5112
|
+
export default defineAgent({
|
|
5113
|
+
result: Result,
|
|
5114
|
+
});
|
|
5115
|
+
|
|
5116
|
+
For schema agents, data is the raw final model text and result is the parsed,
|
|
4915
5117
|
validated object. Use result for application logic; use data only when you want
|
|
4916
5118
|
to inspect the original text. atlasflow run --json prints { ok, runId, data,
|
|
4917
5119
|
result, usage } for piping into tests or automation.`,
|
|
@@ -4957,7 +5159,13 @@ Agents and workflows are plain HTTP, so any CI can call a deployed AtlasFlow:
|
|
|
4957
5159
|
-H "authorization: Bearer $ATLASFLOW_API_KEY" \\
|
|
4958
5160
|
-H "content-type: application/json" \\
|
|
4959
5161
|
-d '{"payload": {"issue": 123}, "wait": true}'
|
|
4960
|
-
Use requireApiKey() (ATLASFLOW_API_KEY) to protect the endpoints
|
|
5162
|
+
Use requireApiKey() (ATLASFLOW_API_KEY) to protect the endpoints.`,
|
|
5163
|
+
skill: `AtlasFlow assistant skill
|
|
5164
|
+
|
|
5165
|
+
This repository ships an assistant skill for coding agents working on AtlasFlow apps:
|
|
5166
|
+
npx skills add <repo>/skills/atlasflow
|
|
5167
|
+
|
|
5168
|
+
The skill tells the coding agent to read local AtlasFlow docs first, inspect the visible agent/ folder, run atlasflow info before guessing about discovery, and verify package/release work with real install and tarball evidence.`
|
|
4961
5169
|
};
|
|
4962
5170
|
async function docsCommand(args) {
|
|
4963
5171
|
const sub = args._[0];
|
|
@@ -6511,6 +6719,8 @@ function uniqueOperationName(base, usedNames) {
|
|
|
6511
6719
|
async function initCommand(args) {
|
|
6512
6720
|
const root = rootOf(args);
|
|
6513
6721
|
const force = args.force === true;
|
|
6722
|
+
const dryRun = args["dry-run"] === true;
|
|
6723
|
+
const noMerge = args["no-merge"] === true;
|
|
6514
6724
|
const target = initTargetOf(str2(args, "target") ?? "node");
|
|
6515
6725
|
const cloudflareRuntime = initCloudflareRuntimeOf(str2(args, "cloudflare-runtime") ?? "worker");
|
|
6516
6726
|
const configPath = path5.join(root, "atlasflow.config.ts");
|
|
@@ -6521,44 +6731,175 @@ async function initCommand(args) {
|
|
|
6521
6731
|
const identityDir = path5.join(agentDir, "identity");
|
|
6522
6732
|
const agentConfigPath = path5.join(agentDir, "agent.jsonc");
|
|
6523
6733
|
const identityPath = path5.join(identityDir, "profile.md");
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
);
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
{
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6734
|
+
const writes = [];
|
|
6735
|
+
writes.push(planScaffoldWrite(configPath, initConfigSource(target, cloudflareRuntime), force));
|
|
6736
|
+
writes.push({
|
|
6737
|
+
...planScaffoldWrite(
|
|
6738
|
+
agentConfigPath,
|
|
6739
|
+
`${JSON.stringify(
|
|
6740
|
+
{
|
|
6741
|
+
$schema: agentConfigSchemaRef(),
|
|
6742
|
+
name: "hello",
|
|
6743
|
+
model: { default: "anthropic/claude-haiku-4-5-20251001" },
|
|
6744
|
+
skills: { loading: "lazy" },
|
|
6745
|
+
prompt: { recipe: "atlas-brain-default" }
|
|
6746
|
+
},
|
|
6747
|
+
null,
|
|
6748
|
+
2
|
|
6749
|
+
)}
|
|
6750
|
+
`,
|
|
6751
|
+
force
|
|
6752
|
+
)
|
|
6753
|
+
});
|
|
6754
|
+
writes.push(planScaffoldWrite(identityPath, "You are a helpful assistant. Answer concisely.\n", force));
|
|
6755
|
+
writes.push(planPackageJsonWrite(packagePath, target, noMerge));
|
|
6756
|
+
writes.push(planTextBlockWrite(envExamplePath, INIT_ENV_EXAMPLE, "AtlasFlow", initEnvExampleLines(), initEnvKey, noMerge));
|
|
6757
|
+
writes.push(planTextBlockWrite(gitignorePath, INIT_GITIGNORE, "AtlasFlow", INIT_GITIGNORE.split("\n").filter(Boolean), void 0, noMerge));
|
|
6758
|
+
if (!dryRun) {
|
|
6759
|
+
fs5.mkdirSync(identityDir, { recursive: true });
|
|
6760
|
+
for (const write of writes) {
|
|
6761
|
+
if (write.action === "skip") continue;
|
|
6762
|
+
fs5.mkdirSync(path5.dirname(write.file), { recursive: true });
|
|
6763
|
+
fs5.writeFileSync(write.file, write.contents);
|
|
6764
|
+
}
|
|
6765
|
+
}
|
|
6766
|
+
console.log(dryRun ? "Dry run: planned scaffold changes:" : "Scaffolded:");
|
|
6767
|
+
for (const write of writes) {
|
|
6768
|
+
const suffix = write.note ? ` - ${write.note}` : "";
|
|
6769
|
+
console.log(` ${write.action.padEnd(9)} ${path5.relative(root, write.file)}${suffix}`);
|
|
6770
|
+
}
|
|
6771
|
+
const install = suggestedInstallCommand(root);
|
|
6772
|
+
console.log(`
|
|
6773
|
+
Next: copy .env.example to .env, set ANTHROPIC_API_KEY, then \`${install} && atlasflow dev\`.`);
|
|
6560
6774
|
console.log("Inspect: atlasflow info");
|
|
6561
6775
|
}
|
|
6776
|
+
function planScaffoldWrite(file, contents, force) {
|
|
6777
|
+
if (!fs5.existsSync(file)) return { file, action: "create", contents };
|
|
6778
|
+
if (force) return { file, action: "overwrite", contents };
|
|
6779
|
+
return { file, action: "skip", contents: fs5.readFileSync(file, "utf8"), note: "exists; use --force to overwrite" };
|
|
6780
|
+
}
|
|
6781
|
+
function planPackageJsonWrite(file, target, noMerge) {
|
|
6782
|
+
const generated = initPackageJsonObject(target);
|
|
6783
|
+
if (!fs5.existsSync(file)) return { file, action: "create", contents: `${JSON.stringify(generated, null, 2)}
|
|
6784
|
+
` };
|
|
6785
|
+
if (noMerge) return { file, action: "skip", contents: fs5.readFileSync(file, "utf8"), note: "--no-merge" };
|
|
6786
|
+
const raw = fs5.readFileSync(file, "utf8");
|
|
6787
|
+
const current = parseJsonObject(raw, file);
|
|
6788
|
+
const { manifest, changed, note } = mergeInitPackageJson(current, generated);
|
|
6789
|
+
return { file, action: changed ? "merge" : "skip", contents: `${JSON.stringify(manifest, null, 2)}
|
|
6790
|
+
`, note };
|
|
6791
|
+
}
|
|
6792
|
+
function mergeInitPackageJson(current, generated) {
|
|
6793
|
+
const manifest = { ...current };
|
|
6794
|
+
let changed = false;
|
|
6795
|
+
const preserved = [];
|
|
6796
|
+
for (const [key, value] of Object.entries(generated)) {
|
|
6797
|
+
if (key === "scripts" || key === "dependencies" || key === "devDependencies") continue;
|
|
6798
|
+
if (manifest[key] === void 0) {
|
|
6799
|
+
manifest[key] = value;
|
|
6800
|
+
changed = true;
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
const scripts = ensurePlainObject(manifest, "scripts");
|
|
6804
|
+
const generatedScripts = objectEntriesOfStrings(generated.scripts);
|
|
6805
|
+
for (const [name, command] of generatedScripts) {
|
|
6806
|
+
const existing = scripts[name];
|
|
6807
|
+
if (existing === void 0) {
|
|
6808
|
+
scripts[name] = command;
|
|
6809
|
+
changed = true;
|
|
6810
|
+
} else if (existing !== command) {
|
|
6811
|
+
const atlasName = `atlasflow:${name}`;
|
|
6812
|
+
if (scripts[atlasName] === void 0) {
|
|
6813
|
+
scripts[atlasName] = command;
|
|
6814
|
+
changed = true;
|
|
6815
|
+
} else if (scripts[atlasName] !== command) {
|
|
6816
|
+
preserved.push(`scripts.${atlasName}`);
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6820
|
+
const existingDeps = dependencyLocations(manifest);
|
|
6821
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
6822
|
+
const target = ensurePlainObject(manifest, section);
|
|
6823
|
+
for (const [name, range] of objectEntriesOfStrings(generated[section])) {
|
|
6824
|
+
if (existingDeps.has(name)) {
|
|
6825
|
+
preserved.push(`${existingDeps.get(name).section}.${name}`);
|
|
6826
|
+
continue;
|
|
6827
|
+
}
|
|
6828
|
+
target[name] = range;
|
|
6829
|
+
existingDeps.set(name, { section, range });
|
|
6830
|
+
changed = true;
|
|
6831
|
+
}
|
|
6832
|
+
}
|
|
6833
|
+
return {
|
|
6834
|
+
manifest,
|
|
6835
|
+
changed,
|
|
6836
|
+
note: preserved.length ? `preserved existing ${[...new Set(preserved)].join(", ")}` : void 0
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6839
|
+
function ensurePlainObject(parent, key) {
|
|
6840
|
+
const current = parent[key];
|
|
6841
|
+
if (current && typeof current === "object" && !Array.isArray(current)) return current;
|
|
6842
|
+
const next = {};
|
|
6843
|
+
parent[key] = next;
|
|
6844
|
+
return next;
|
|
6845
|
+
}
|
|
6846
|
+
function objectEntriesOfStrings(value) {
|
|
6847
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
6848
|
+
return Object.entries(value).filter((entry) => typeof entry[1] === "string");
|
|
6849
|
+
}
|
|
6850
|
+
function dependencyLocations(manifest) {
|
|
6851
|
+
const out = /* @__PURE__ */ new Map();
|
|
6852
|
+
for (const section of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
|
|
6853
|
+
for (const [name, range] of objectEntriesOfStrings(manifest[section])) out.set(name, { section, range });
|
|
6854
|
+
}
|
|
6855
|
+
return out;
|
|
6856
|
+
}
|
|
6857
|
+
function planTextBlockWrite(file, createContents, label, desiredLines, keyOf, noMerge = false) {
|
|
6858
|
+
if (!fs5.existsSync(file)) return { file, action: "create", contents: createContents };
|
|
6859
|
+
const raw = fs5.readFileSync(file, "utf8");
|
|
6860
|
+
if (noMerge) return { file, action: "skip", contents: raw, note: "--no-merge" };
|
|
6861
|
+
const merged = mergeTextBlock(raw, label, desiredLines, keyOf);
|
|
6862
|
+
return {
|
|
6863
|
+
file,
|
|
6864
|
+
action: merged.changed ? "merge" : "skip",
|
|
6865
|
+
contents: merged.contents,
|
|
6866
|
+
note: merged.added.length ? `added ${merged.added.join(", ")}` : void 0
|
|
6867
|
+
};
|
|
6868
|
+
}
|
|
6869
|
+
function mergeTextBlock(raw, label, desiredLines, keyOf) {
|
|
6870
|
+
const normalized = raw.replace(/\r\n/g, "\n");
|
|
6871
|
+
const existingLines = new Set(normalized.split("\n").map((line) => line.trim()));
|
|
6872
|
+
const existingKeys = new Set(normalized.split("\n").map((line) => keyOf?.(line.trim())).filter((key) => Boolean(key)));
|
|
6873
|
+
const missing = desiredLines.filter((line) => {
|
|
6874
|
+
const key = keyOf?.(line);
|
|
6875
|
+
if (key) return !existingKeys.has(key);
|
|
6876
|
+
return line.trim() && !existingLines.has(line.trim());
|
|
6877
|
+
});
|
|
6878
|
+
if (!missing.length) return { contents: raw, changed: false, added: [] };
|
|
6879
|
+
const prefix = normalized.endsWith("\n") || normalized.length === 0 ? "" : "\n";
|
|
6880
|
+
const block = [`# ${label}`, ...missing, ""].join("\n");
|
|
6881
|
+
return { contents: `${normalized}${prefix}${block}`, changed: true, added: missing.map((line) => keyOf?.(line) ?? line) };
|
|
6882
|
+
}
|
|
6883
|
+
function initEnvExampleLines() {
|
|
6884
|
+
return INIT_ENV_EXAMPLE.split("\n").filter((line) => {
|
|
6885
|
+
const trimmed = line.trim();
|
|
6886
|
+
return trimmed && trimmed !== "# Copy this file to .env for local development.";
|
|
6887
|
+
});
|
|
6888
|
+
}
|
|
6889
|
+
function initEnvKey(line) {
|
|
6890
|
+
const match = line.match(/^\s*#?\s*([A-Z][A-Z0-9_]+)=/);
|
|
6891
|
+
return match?.[1];
|
|
6892
|
+
}
|
|
6893
|
+
function parseJsonObject(raw, file) {
|
|
6894
|
+
const parsed = JSON.parse(raw);
|
|
6895
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`Invalid JSON object: ${file}`);
|
|
6896
|
+
return parsed;
|
|
6897
|
+
}
|
|
6898
|
+
function suggestedInstallCommand(root) {
|
|
6899
|
+
if (fs5.existsSync(path5.join(root, "pnpm-lock.yaml"))) return "pnpm install";
|
|
6900
|
+
if (fs5.existsSync(path5.join(root, "yarn.lock"))) return "yarn install";
|
|
6901
|
+
return "npm install";
|
|
6902
|
+
}
|
|
6562
6903
|
function initTargetOf(value) {
|
|
6563
6904
|
if (value === "node" || value === "cloudflare") return value;
|
|
6564
6905
|
throw new Error('atlasflow init target must be "node" or "cloudflare"');
|
|
@@ -6578,9 +6919,9 @@ function initConfigSource(target, cloudflareRuntime) {
|
|
|
6578
6919
|
lines.push("});", "");
|
|
6579
6920
|
return lines.join("\n");
|
|
6580
6921
|
}
|
|
6581
|
-
function
|
|
6922
|
+
function initPackageJsonObject(target) {
|
|
6582
6923
|
const version = atlasflowPackageVersion();
|
|
6583
|
-
|
|
6924
|
+
return {
|
|
6584
6925
|
name: "atlasflow-app",
|
|
6585
6926
|
private: true,
|
|
6586
6927
|
type: "module",
|
|
@@ -6600,8 +6941,6 @@ function initPackageJson(target) {
|
|
|
6600
6941
|
...target === "cloudflare" ? { wrangler: "^4.87.0" } : {}
|
|
6601
6942
|
}
|
|
6602
6943
|
};
|
|
6603
|
-
return `${JSON.stringify(json, null, 2)}
|
|
6604
|
-
`;
|
|
6605
6944
|
}
|
|
6606
6945
|
var INIT_ENV_EXAMPLE = [
|
|
6607
6946
|
"# Copy this file to .env for local development.",
|
|
@@ -6645,6 +6984,7 @@ Usage:
|
|
|
6645
6984
|
atlasflow dev [--port <n>] [--console] watch + serve locally; optional operator console
|
|
6646
6985
|
atlasflow build [--output <dir>] bundle a deployable Node server
|
|
6647
6986
|
atlasflow deploy [--dry-run] build and deploy one Worker via Wrangler
|
|
6987
|
+
atlasflow doctor diagnose setup and print the next fix
|
|
6648
6988
|
atlasflow info [--json|--check] inspect or verify the discovered agent graph
|
|
6649
6989
|
atlasflow run [agent] [--message <t>] invoke an agent once (CI-style)
|
|
6650
6990
|
[--payload <json>]
|
|
@@ -6664,6 +7004,7 @@ Usage:
|
|
|
6664
7004
|
Common flags: --root <dir>, --target node|cloudflare, --cloudflare-runtime worker|agents
|
|
6665
7005
|
`;
|
|
6666
7006
|
async function main(argv) {
|
|
7007
|
+
assertSupportedNode(process.versions.node);
|
|
6667
7008
|
const [command, ...rest] = argv;
|
|
6668
7009
|
const args = parseArgs(rest);
|
|
6669
7010
|
switch (command) {
|
|
@@ -6675,6 +7016,8 @@ async function main(argv) {
|
|
|
6675
7016
|
return buildCommand(args);
|
|
6676
7017
|
case "deploy":
|
|
6677
7018
|
return deployCommand(args);
|
|
7019
|
+
case "doctor":
|
|
7020
|
+
return doctorCommand(args);
|
|
6678
7021
|
case "info":
|
|
6679
7022
|
return infoCommand(args);
|
|
6680
7023
|
case "run":
|
|
@@ -6700,10 +7043,17 @@ async function main(argv) {
|
|
|
6700
7043
|
process.stdout.write(HELP);
|
|
6701
7044
|
return;
|
|
6702
7045
|
default:
|
|
6703
|
-
throw new Error(`Unknown command "${command}". Try: init, dev, build, deploy, info, run, connect, eval, logs, add, export, import, docs`);
|
|
7046
|
+
throw new Error(`Unknown command "${command}". Try: init, dev, build, deploy, doctor, info, run, connect, eval, logs, add, export, import, docs`);
|
|
7047
|
+
}
|
|
7048
|
+
}
|
|
7049
|
+
function assertSupportedNode(version) {
|
|
7050
|
+
const major = Number(version.split(".")[0]);
|
|
7051
|
+
if (!Number.isFinite(major) || major < 22) {
|
|
7052
|
+
throw new Error(`AtlasFlow requires Node.js 22 or newer. Current Node.js: ${version}. Next: install Node 22+ and rerun the command.`);
|
|
6704
7053
|
}
|
|
6705
7054
|
}
|
|
6706
7055
|
export {
|
|
7056
|
+
assertSupportedNode,
|
|
6707
7057
|
defineConfig,
|
|
6708
7058
|
main
|
|
6709
7059
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cybernetyx1/atlasflow-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Command-line tool for authoring, inspecting, building, and deploying AtlasFlow agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"esbuild": "^0.24.0",
|
|
29
29
|
"yaml": "^2.9.0",
|
|
30
|
-
"@cybernetyx1/atlasflow-runtime": "0.1.
|
|
30
|
+
"@cybernetyx1/atlasflow-runtime": "0.1.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.10.0",
|