@agent-native/core 0.52.0 → 0.54.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 +41 -95
- package/blueprints/action/crud.md +98 -0
- package/blueprints/channel/discord.md +74 -0
- package/blueprints/provider/stripe.md +87 -0
- package/blueprints/sandbox/docker.md +78 -0
- package/dist/action.d.ts +64 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +73 -2
- package/dist/action.js.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +1 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/observational-memory/compactor.d.ts +43 -0
- package/dist/agent/observational-memory/compactor.d.ts.map +1 -0
- package/dist/agent/observational-memory/compactor.js +50 -0
- package/dist/agent/observational-memory/compactor.js.map +1 -0
- package/dist/agent/observational-memory/config.d.ts +37 -0
- package/dist/agent/observational-memory/config.d.ts.map +1 -0
- package/dist/agent/observational-memory/config.js +48 -0
- package/dist/agent/observational-memory/config.js.map +1 -0
- package/dist/agent/observational-memory/index.d.ts +26 -0
- package/dist/agent/observational-memory/index.d.ts.map +1 -0
- package/dist/agent/observational-memory/index.js +25 -0
- package/dist/agent/observational-memory/index.js.map +1 -0
- package/dist/agent/observational-memory/internal-run.d.ts +37 -0
- package/dist/agent/observational-memory/internal-run.d.ts.map +1 -0
- package/dist/agent/observational-memory/internal-run.js +59 -0
- package/dist/agent/observational-memory/internal-run.js.map +1 -0
- package/dist/agent/observational-memory/message-text.d.ts +13 -0
- package/dist/agent/observational-memory/message-text.d.ts.map +1 -0
- package/dist/agent/observational-memory/message-text.js +46 -0
- package/dist/agent/observational-memory/message-text.js.map +1 -0
- package/dist/agent/observational-memory/migrations.d.ts +13 -0
- package/dist/agent/observational-memory/migrations.d.ts.map +1 -0
- package/dist/agent/observational-memory/migrations.js +43 -0
- package/dist/agent/observational-memory/migrations.js.map +1 -0
- package/dist/agent/observational-memory/observer.d.ts +37 -0
- package/dist/agent/observational-memory/observer.d.ts.map +1 -0
- package/dist/agent/observational-memory/observer.js +82 -0
- package/dist/agent/observational-memory/observer.js.map +1 -0
- package/dist/agent/observational-memory/plugin.d.ts +16 -0
- package/dist/agent/observational-memory/plugin.d.ts.map +1 -0
- package/dist/agent/observational-memory/plugin.js +26 -0
- package/dist/agent/observational-memory/plugin.js.map +1 -0
- package/dist/agent/observational-memory/prompts.d.ts +27 -0
- package/dist/agent/observational-memory/prompts.d.ts.map +1 -0
- package/dist/agent/observational-memory/prompts.js +42 -0
- package/dist/agent/observational-memory/prompts.js.map +1 -0
- package/dist/agent/observational-memory/read.d.ts +45 -0
- package/dist/agent/observational-memory/read.d.ts.map +1 -0
- package/dist/agent/observational-memory/read.js +97 -0
- package/dist/agent/observational-memory/read.js.map +1 -0
- package/dist/agent/observational-memory/reflector.d.ts +31 -0
- package/dist/agent/observational-memory/reflector.d.ts.map +1 -0
- package/dist/agent/observational-memory/reflector.js +76 -0
- package/dist/agent/observational-memory/reflector.js.map +1 -0
- package/dist/agent/observational-memory/schema.d.ts +267 -0
- package/dist/agent/observational-memory/schema.d.ts.map +1 -0
- package/dist/agent/observational-memory/schema.js +48 -0
- package/dist/agent/observational-memory/schema.js.map +1 -0
- package/dist/agent/observational-memory/store.d.ts +52 -0
- package/dist/agent/observational-memory/store.d.ts.map +1 -0
- package/dist/agent/observational-memory/store.js +197 -0
- package/dist/agent/observational-memory/store.js.map +1 -0
- package/dist/agent/observational-memory/types.d.ts +61 -0
- package/dist/agent/observational-memory/types.d.ts.map +1 -0
- package/dist/agent/observational-memory/types.js +9 -0
- package/dist/agent/observational-memory/types.js.map +1 -0
- package/dist/agent/processors.d.ts +146 -0
- package/dist/agent/processors.d.ts.map +1 -0
- package/dist/agent/processors.js +122 -0
- package/dist/agent/processors.js.map +1 -0
- package/dist/agent/production-agent.d.ts +25 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +341 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +48 -0
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-store.d.ts +17 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +55 -0
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/runtime-context.d.ts +30 -0
- package/dist/agent/runtime-context.d.ts.map +1 -1
- package/dist/agent/runtime-context.js +54 -1
- package/dist/agent/runtime-context.js.map +1 -1
- package/dist/agent/tool-call-journal.d.ts +99 -0
- package/dist/agent/tool-call-journal.d.ts.map +1 -0
- package/dist/agent/tool-call-journal.js +212 -0
- package/dist/agent/tool-call-journal.js.map +1 -0
- package/dist/agent/types.d.ts +35 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/add.d.ts +109 -0
- package/dist/cli/add.d.ts.map +1 -0
- package/dist/cli/add.js +352 -0
- package/dist/cli/add.js.map +1 -0
- package/dist/cli/connect.d.ts +2 -2
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +92 -24
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/eval.d.ts +17 -0
- package/dist/cli/eval.d.ts.map +1 -0
- package/dist/cli/eval.js +121 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/index.js +44 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +11 -5
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/plan-local.d.ts +66 -5
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +622 -21
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +2 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +108 -62
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +118 -92
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +16 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +20 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +32 -7
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +13 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +21 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +18 -2
- package/dist/coding-tools/run-code.js.map +1 -1
- package/dist/db/client.d.ts +4 -2
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +6 -4
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/route-discovery.d.ts.map +1 -1
- package/dist/deploy/route-discovery.js +1 -0
- package/dist/deploy/route-discovery.js.map +1 -1
- package/dist/eval/agent-runner.d.ts +63 -0
- package/dist/eval/agent-runner.d.ts.map +1 -0
- package/dist/eval/agent-runner.js +142 -0
- package/dist/eval/agent-runner.js.map +1 -0
- package/dist/eval/define-eval.d.ts +29 -0
- package/dist/eval/define-eval.d.ts.map +1 -0
- package/dist/eval/define-eval.js +43 -0
- package/dist/eval/define-eval.js.map +1 -0
- package/dist/eval/index.d.ts +18 -0
- package/dist/eval/index.d.ts.map +1 -0
- package/dist/eval/index.js +17 -0
- package/dist/eval/index.js.map +1 -0
- package/dist/eval/report.d.ts +8 -0
- package/dist/eval/report.d.ts.map +1 -0
- package/dist/eval/report.js +44 -0
- package/dist/eval/report.js.map +1 -0
- package/dist/eval/runner.d.ts +67 -0
- package/dist/eval/runner.d.ts.map +1 -0
- package/dist/eval/runner.js +256 -0
- package/dist/eval/runner.js.map +1 -0
- package/dist/eval/scorer.d.ts +83 -0
- package/dist/eval/scorer.d.ts.map +1 -0
- package/dist/eval/scorer.js +195 -0
- package/dist/eval/scorer.js.map +1 -0
- package/dist/eval/types.d.ts +162 -0
- package/dist/eval/types.d.ts.map +1 -0
- package/dist/eval/types.js +20 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/extensions/fetch-tool.d.ts.map +1 -1
- package/dist/extensions/fetch-tool.js +80 -15
- package/dist/extensions/fetch-tool.js.map +1 -1
- package/dist/extensions/web-content.d.ts +61 -0
- package/dist/extensions/web-content.d.ts.map +1 -0
- package/dist/extensions/web-content.js +468 -0
- package/dist/extensions/web-content.js.map +1 -0
- package/dist/extensions/web-search-tool.js +3 -3
- package/dist/extensions/web-search-tool.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +4 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +100 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/observability/tracing.d.ts +73 -0
- package/dist/observability/tracing.d.ts.map +1 -0
- package/dist/observability/tracing.js +126 -0
- package/dist/observability/tracing.js.map +1 -0
- package/dist/onboarding/default-steps.d.ts.map +1 -1
- package/dist/onboarding/default-steps.js +4 -1
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/provider-api/actions/query-staged-dataset.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +80 -0
- package/dist/provider-api/corpus-jobs.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs.js +219 -22
- package/dist/provider-api/corpus-jobs.js.map +1 -1
- package/dist/provider-api/index.d.ts +24 -32
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +28 -1
- package/dist/provider-api/index.js.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
- package/dist/scripts/agent-engines/list-agent-engines.js +10 -3
- package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +4 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +9 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +119 -111
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-teams.d.ts +62 -0
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +99 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +7 -0
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +90 -0
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +7 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +2 -0
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/deep-link.d.ts +7 -0
- package/dist/server/deep-link.d.ts.map +1 -1
- package/dist/server/deep-link.js +13 -2
- package/dist/server/deep-link.js.map +1 -1
- package/dist/server/framework-request-handler.d.ts.map +1 -1
- package/dist/server/framework-request-handler.js +33 -1
- package/dist/server/framework-request-handler.js.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -1
- package/dist/server/index.js.map +1 -1
- package/dist/templates/default/.agents/skills/actions/SKILL.md +52 -1
- package/dist/templates/default/.agents/skills/security/SKILL.md +22 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +52 -1
- package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +16 -4
- package/dist/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +20 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +31 -0
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +22 -0
- package/docs/content/actions.md +50 -0
- package/docs/content/agent-teams.md +32 -0
- package/docs/content/blueprint-installer.md +73 -0
- package/docs/content/durable-resume.md +49 -0
- package/docs/content/evals.md +141 -0
- package/docs/content/external-agents.md +2 -2
- package/docs/content/human-approval.md +101 -0
- package/docs/content/observability.md +21 -0
- package/docs/content/observational-memory.md +63 -0
- package/docs/content/plan-plugin.md +5 -0
- package/docs/content/pr-visual-recap.md +9 -5
- package/docs/content/processors.md +99 -0
- package/docs/content/sandbox-adapters.md +134 -0
- package/docs/content/template-plan.md +97 -21
- package/package.json +10 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +52 -1
- package/src/templates/default/.agents/skills/security/SKILL.md +22 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +52 -1
- package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +16 -4
- package/src/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +20 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +31 -0
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +22 -0
package/dist/cli/plan-local.js
CHANGED
|
@@ -2,17 +2,30 @@
|
|
|
2
2
|
* Plan helper commands.
|
|
3
3
|
*
|
|
4
4
|
* The `plan local` commands are intentionally separate from the Plan app
|
|
5
|
-
* actions. They do not call MCP,
|
|
6
|
-
* they only read
|
|
7
|
-
* auditable no-DB path. The top-level
|
|
8
|
-
* no-auth helper for fetching the
|
|
9
|
-
* MDX; it never sends plan content.
|
|
5
|
+
* actions. They do not call MCP, hosted write actions, SQLite, or hosted
|
|
6
|
+
* storage; they only read local files or serve them from a localhost bridge so
|
|
7
|
+
* privacy-focused users have an auditable no-DB path. The top-level
|
|
8
|
+
* `plan blocks` command is a schema-only, no-auth helper for fetching the
|
|
9
|
+
* public block catalog before authoring local MDX; it never sends plan content.
|
|
10
10
|
*/
|
|
11
11
|
import fs from "node:fs";
|
|
12
|
+
import crypto from "node:crypto";
|
|
13
|
+
import http from "node:http";
|
|
12
14
|
import path from "node:path";
|
|
13
15
|
import { spawnSync } from "node:child_process";
|
|
14
16
|
import { pathToFileURL } from "node:url";
|
|
15
17
|
import { DEFAULT_PLAN_APP_URL, defaultPlanBlocksOut, fetchPlanBlockCatalog, normalizePlanBlockFormat, } from "./plan-blocks.js";
|
|
18
|
+
const LOCAL_PLAN_ASSET_MAX_SINGLE_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
const LOCAL_PLAN_ASSET_MAX_TOTAL_BYTES = 10 * 1024 * 1024;
|
|
20
|
+
const AGENT_NATIVE_MANIFEST_FILE = "agent-native.json";
|
|
21
|
+
const LOCAL_PLAN_ASSET_EXTENSIONS = new Set([
|
|
22
|
+
"png",
|
|
23
|
+
"jpg",
|
|
24
|
+
"jpeg",
|
|
25
|
+
"gif",
|
|
26
|
+
"webp",
|
|
27
|
+
"svg",
|
|
28
|
+
]);
|
|
16
29
|
function parseArgs(argv) {
|
|
17
30
|
const out = {};
|
|
18
31
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -62,19 +75,135 @@ function normalizeKind(value) {
|
|
|
62
75
|
return value;
|
|
63
76
|
throw new Error(`Invalid --kind "${value}" (expected plan or recap)`);
|
|
64
77
|
}
|
|
78
|
+
function normalizeSlash(filePath) {
|
|
79
|
+
return filePath.replace(/\\/g, "/");
|
|
80
|
+
}
|
|
81
|
+
function normalizeRelativePath(filePath) {
|
|
82
|
+
if (!filePath.trim() || path.isAbsolute(filePath))
|
|
83
|
+
return null;
|
|
84
|
+
const normalized = path.posix
|
|
85
|
+
.normalize(normalizeSlash(filePath.trim()))
|
|
86
|
+
.replace(/\/+$/, "");
|
|
87
|
+
if (!normalized ||
|
|
88
|
+
normalized === "." ||
|
|
89
|
+
normalized === ".." ||
|
|
90
|
+
normalized.startsWith("../") ||
|
|
91
|
+
normalized.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return normalized;
|
|
95
|
+
}
|
|
96
|
+
function findUpward(startDir, filename) {
|
|
97
|
+
let current = path.resolve(startDir);
|
|
98
|
+
for (;;) {
|
|
99
|
+
const candidate = path.join(current, filename);
|
|
100
|
+
if (fs.existsSync(candidate))
|
|
101
|
+
return candidate;
|
|
102
|
+
const parent = path.dirname(current);
|
|
103
|
+
if (parent === current)
|
|
104
|
+
return null;
|
|
105
|
+
current = parent;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isRecord(value) {
|
|
109
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
110
|
+
}
|
|
111
|
+
function manifestRootPath(value) {
|
|
112
|
+
if (typeof value === "string")
|
|
113
|
+
return normalizeRelativePath(value);
|
|
114
|
+
if (isRecord(value) && typeof value.path === "string") {
|
|
115
|
+
return normalizeRelativePath(value.path);
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function planManifestConfig() {
|
|
120
|
+
const configuredManifest = process.env.AGENT_NATIVE_MANIFEST?.trim() ||
|
|
121
|
+
process.env.AGENT_NATIVE_MANIFEST_PATH?.trim();
|
|
122
|
+
const manifestPath = configuredManifest
|
|
123
|
+
? path.resolve(configuredManifest)
|
|
124
|
+
: findUpward(process.cwd(), AGENT_NATIVE_MANIFEST_FILE);
|
|
125
|
+
if (!manifestPath)
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
129
|
+
const apps = isRecord(parsed) && isRecord(parsed.apps) ? parsed.apps : null;
|
|
130
|
+
const planApp = apps && isRecord(apps.plan) ? apps.plan : null;
|
|
131
|
+
const roots = planApp && Array.isArray(planApp.roots) ? planApp.roots : [];
|
|
132
|
+
const plansPath = roots
|
|
133
|
+
.map(manifestRootPath)
|
|
134
|
+
.find((item) => Boolean(item));
|
|
135
|
+
return plansPath
|
|
136
|
+
? { rootDir: path.dirname(manifestPath), plansPath }
|
|
137
|
+
: null;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function localPlanWorkspaceRoot(startDir = process.cwd()) {
|
|
144
|
+
const manifestPath = findUpward(startDir, AGENT_NATIVE_MANIFEST_FILE);
|
|
145
|
+
if (manifestPath)
|
|
146
|
+
return path.dirname(manifestPath);
|
|
147
|
+
const gitDir = findUpward(startDir, ".git");
|
|
148
|
+
if (gitDir)
|
|
149
|
+
return path.dirname(gitDir);
|
|
150
|
+
const configuredManifest = process.env.AGENT_NATIVE_MANIFEST?.trim() ||
|
|
151
|
+
process.env.AGENT_NATIVE_MANIFEST_PATH?.trim();
|
|
152
|
+
if (configuredManifest)
|
|
153
|
+
return path.dirname(path.resolve(configuredManifest));
|
|
154
|
+
return process.cwd();
|
|
155
|
+
}
|
|
65
156
|
function defaultPlansDir() {
|
|
66
|
-
|
|
157
|
+
const configured = process.env.PLAN_LOCAL_DIR;
|
|
158
|
+
if (configured?.trim())
|
|
159
|
+
return path.resolve(configured.trim());
|
|
160
|
+
const manifest = planManifestConfig();
|
|
161
|
+
if (manifest)
|
|
162
|
+
return path.resolve(manifest.rootDir, manifest.plansPath);
|
|
163
|
+
return path.resolve("plans");
|
|
67
164
|
}
|
|
68
165
|
function defaultLocalPlanAppUrl() {
|
|
69
166
|
return (process.env.PLAN_LOCAL_APP_URL ||
|
|
70
167
|
process.env.PLAN_BASE_URL ||
|
|
71
168
|
"http://localhost:8096");
|
|
72
169
|
}
|
|
170
|
+
function defaultLocalPlanBridgeAppUrl() {
|
|
171
|
+
return (process.env.PLAN_LOCAL_BRIDGE_APP_URL ||
|
|
172
|
+
process.env.PLAN_BASE_URL ||
|
|
173
|
+
DEFAULT_PLAN_APP_URL);
|
|
174
|
+
}
|
|
73
175
|
function normalizeAppUrl(value) {
|
|
74
176
|
return (value || defaultLocalPlanAppUrl()).replace(/\/+$/, "");
|
|
75
177
|
}
|
|
178
|
+
function normalizeBridgeAppUrl(value) {
|
|
179
|
+
return (value || defaultLocalPlanBridgeAppUrl()).replace(/\/+$/, "");
|
|
180
|
+
}
|
|
76
181
|
function localPlanPreviewUrl(dir, appUrl) {
|
|
77
|
-
|
|
182
|
+
const base = `${normalizeAppUrl(appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(dir)))}`;
|
|
183
|
+
const repoPath = repoRelativePlanPath(dir);
|
|
184
|
+
if (!repoPath)
|
|
185
|
+
return base;
|
|
186
|
+
return `${base}?${new URLSearchParams({ path: repoPath }).toString()}`;
|
|
187
|
+
}
|
|
188
|
+
function realpathIfExists(filePath) {
|
|
189
|
+
try {
|
|
190
|
+
return fs.realpathSync.native(filePath);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return path.resolve(filePath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function repoRelativePlanPath(dir) {
|
|
197
|
+
const resolved = realpathIfExists(dir);
|
|
198
|
+
const root = realpathIfExists(localPlanWorkspaceRoot(resolved));
|
|
199
|
+
const relative = path.relative(root, resolved);
|
|
200
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return normalizeSlash(relative);
|
|
204
|
+
}
|
|
205
|
+
function localPlanBridgePageUrl(input) {
|
|
206
|
+
return `${normalizeBridgeAppUrl(input.appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(input.dir)))}?bridge=${encodeURIComponent(input.bridgeUrl)}`;
|
|
78
207
|
}
|
|
79
208
|
function openLocalUrl(url) {
|
|
80
209
|
const platform = process.platform;
|
|
@@ -241,6 +370,33 @@ function renderMarkdownish(source) {
|
|
|
241
370
|
flushList();
|
|
242
371
|
return html.join("\n");
|
|
243
372
|
}
|
|
373
|
+
function readLocalPlanAssets(dir) {
|
|
374
|
+
const assetsDir = path.join(dir, "assets");
|
|
375
|
+
if (!fs.existsSync(assetsDir))
|
|
376
|
+
return undefined;
|
|
377
|
+
const assets = {};
|
|
378
|
+
let totalBytes = 0;
|
|
379
|
+
for (const entry of fs.readdirSync(assetsDir, { withFileTypes: true })) {
|
|
380
|
+
if (!entry.isFile())
|
|
381
|
+
continue;
|
|
382
|
+
const filename = path.basename(entry.name);
|
|
383
|
+
if (!filename || filename !== entry.name)
|
|
384
|
+
continue;
|
|
385
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
386
|
+
if (!LOCAL_PLAN_ASSET_EXTENSIONS.has(ext))
|
|
387
|
+
continue;
|
|
388
|
+
const abs = path.join(assetsDir, filename);
|
|
389
|
+
const bytes = fs.readFileSync(abs);
|
|
390
|
+
if (bytes.byteLength > LOCAL_PLAN_ASSET_MAX_SINGLE_BYTES)
|
|
391
|
+
continue;
|
|
392
|
+
if (totalBytes + bytes.byteLength > LOCAL_PLAN_ASSET_MAX_TOTAL_BYTES) {
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
totalBytes += bytes.byteLength;
|
|
396
|
+
assets[filename] = bytes.toString("base64");
|
|
397
|
+
}
|
|
398
|
+
return Object.keys(assets).length > 0 ? assets : undefined;
|
|
399
|
+
}
|
|
244
400
|
export function readLocalPlanFiles(dir) {
|
|
245
401
|
const resolved = path.resolve(dir);
|
|
246
402
|
const planPath = path.join(resolved, "plan.mdx");
|
|
@@ -257,10 +413,255 @@ export function readLocalPlanFiles(dir) {
|
|
|
257
413
|
canvasMdx: readOptional("canvas.mdx"),
|
|
258
414
|
prototypeMdx: readOptional("prototype.mdx"),
|
|
259
415
|
stateJson: readOptional(".plan-state.json"),
|
|
416
|
+
assets: readLocalPlanAssets(resolved),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function localPlanMdxFolder(files) {
|
|
420
|
+
return {
|
|
421
|
+
"plan.mdx": files.planMdx,
|
|
422
|
+
...(files.canvasMdx ? { "canvas.mdx": files.canvasMdx } : {}),
|
|
423
|
+
...(files.prototypeMdx ? { "prototype.mdx": files.prototypeMdx } : {}),
|
|
424
|
+
...(files.stateJson ? { ".plan-state.json": files.stateJson } : {}),
|
|
425
|
+
...(files.assets ? { "assets/": files.assets } : {}),
|
|
260
426
|
};
|
|
261
427
|
}
|
|
428
|
+
function localPlanFileList(files) {
|
|
429
|
+
return [
|
|
430
|
+
"plan.mdx",
|
|
431
|
+
...(files.canvasMdx ? ["canvas.mdx"] : []),
|
|
432
|
+
...(files.prototypeMdx ? ["prototype.mdx"] : []),
|
|
433
|
+
...(files.stateJson ? [".plan-state.json"] : []),
|
|
434
|
+
...Object.keys(files.assets ?? {}).map((filename) => `assets/${filename}`),
|
|
435
|
+
];
|
|
436
|
+
}
|
|
437
|
+
function localPlanSourceEntries(files) {
|
|
438
|
+
return [
|
|
439
|
+
{ file: "plan.mdx", source: files.planMdx },
|
|
440
|
+
...(files.canvasMdx
|
|
441
|
+
? [{ file: "canvas.mdx", source: files.canvasMdx }]
|
|
442
|
+
: []),
|
|
443
|
+
...(files.prototypeMdx
|
|
444
|
+
? [{ file: "prototype.mdx", source: files.prototypeMdx }]
|
|
445
|
+
: []),
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
function lineNumberAt(source, index) {
|
|
449
|
+
let line = 1;
|
|
450
|
+
for (let i = 0; i < index; i += 1) {
|
|
451
|
+
if (source.charCodeAt(i) === 10)
|
|
452
|
+
line += 1;
|
|
453
|
+
}
|
|
454
|
+
return line;
|
|
455
|
+
}
|
|
456
|
+
function maskFencedCode(source) {
|
|
457
|
+
return source.replace(/(^|\n)(```|~~~)[\s\S]*?(\n\2[^\n]*(?=\n|$))/g, (match) => match.replace(/[^\n]/g, " "));
|
|
458
|
+
}
|
|
459
|
+
function findJsxOpeningTagEnd(source, start) {
|
|
460
|
+
let quote = null;
|
|
461
|
+
let braceDepth = 0;
|
|
462
|
+
for (let i = start; i < source.length; i += 1) {
|
|
463
|
+
const char = source[i];
|
|
464
|
+
if (quote) {
|
|
465
|
+
if (char === "\\" && i + 1 < source.length) {
|
|
466
|
+
i += 1;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (char === quote)
|
|
470
|
+
quote = null;
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
474
|
+
quote = char;
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (char === "{") {
|
|
478
|
+
braceDepth += 1;
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (char === "}") {
|
|
482
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (char === ">" && braceDepth === 0)
|
|
486
|
+
return i;
|
|
487
|
+
}
|
|
488
|
+
return -1;
|
|
489
|
+
}
|
|
490
|
+
function addValidationIssue(issues, file, source, index, message) {
|
|
491
|
+
issues.push({ file, line: lineNumberAt(source, index), message });
|
|
492
|
+
}
|
|
493
|
+
const ENTITY_RE = /&(?:[a-z][a-z0-9]+|#[0-9]+|#x[0-9a-f]+);/gi;
|
|
494
|
+
const HTML_TEXT_ATTR_RE = /\b(?:aria-label|alt|placeholder|title|value)=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/gi;
|
|
495
|
+
const WIREFRAME_TEXT_ATTR_RE = /\b(?:text|value|label|placeholder|title|note|due)=\s*(?:"([^"]*)"|'([^']*)'|`([^`]*)`)/gi;
|
|
496
|
+
function normalizeVisibleText(value) {
|
|
497
|
+
return value
|
|
498
|
+
.replace(/ | |�*a0;/gi, " ")
|
|
499
|
+
.replace(ENTITY_RE, "x")
|
|
500
|
+
.replace(/\s+/g, " ")
|
|
501
|
+
.trim();
|
|
502
|
+
}
|
|
503
|
+
function meaningfulTextLength(value) {
|
|
504
|
+
return normalizeVisibleText(value ?? "").length;
|
|
505
|
+
}
|
|
506
|
+
function htmlMeaningfulTextLength(html) {
|
|
507
|
+
let length = 0;
|
|
508
|
+
for (const match of html.matchAll(HTML_TEXT_ATTR_RE)) {
|
|
509
|
+
length += meaningfulTextLength(match[1] ?? match[2] ?? match[3]);
|
|
510
|
+
}
|
|
511
|
+
const visibleText = html
|
|
512
|
+
.replace(/<!--[\s\S]*?-->/g, " ")
|
|
513
|
+
.replace(/<script\b[\s\S]*?<\/script>/gi, " ")
|
|
514
|
+
.replace(/<style\b[\s\S]*?<\/style>/gi, " ")
|
|
515
|
+
.replace(/<[^>]+>/g, " ");
|
|
516
|
+
length += meaningfulTextLength(visibleText);
|
|
517
|
+
return length;
|
|
518
|
+
}
|
|
519
|
+
function hasSkeletonGeometry(html) {
|
|
520
|
+
return (/<(?:div|span|section|main|article|ul|li)\b/i.test(html) &&
|
|
521
|
+
/\b(?:height|width|background|border|padding|wf-card|wf-box|wf-pill|wf-chip)\b/i.test(html));
|
|
522
|
+
}
|
|
523
|
+
function stringAttributeValues(source, name) {
|
|
524
|
+
const values = [];
|
|
525
|
+
const re = new RegExp(`\\b${name}\\s*=\\s*(?:\\{\\s*\`([\\s\\S]*?)\`\\s*\\}|\\{\\s*"([^"]*)"\\s*\\}|\\{\\s*'([^']*)'\\s*\\}|"([^"]*)"|'([^']*)')`, "g");
|
|
526
|
+
for (const match of source.matchAll(re)) {
|
|
527
|
+
const value = match[1] ?? match[2] ?? match[3] ?? match[4] ?? match[5];
|
|
528
|
+
if (value !== undefined)
|
|
529
|
+
values.push(value);
|
|
530
|
+
}
|
|
531
|
+
return values;
|
|
532
|
+
}
|
|
533
|
+
function hasUnparsedAttributeExpression(source, name) {
|
|
534
|
+
return new RegExp(`\\b${name}\\s*=\\s*\\{`).test(source);
|
|
535
|
+
}
|
|
536
|
+
function hasMeaningfulWireframeHtml(screenOpening) {
|
|
537
|
+
const htmlValues = stringAttributeValues(screenOpening, "html");
|
|
538
|
+
if (htmlValues.length === 0) {
|
|
539
|
+
return hasUnparsedAttributeExpression(screenOpening, "html") ? null : false;
|
|
540
|
+
}
|
|
541
|
+
return htmlValues.some((html) => htmlMeaningfulTextLength(html) >= 2 || hasSkeletonGeometry(html));
|
|
542
|
+
}
|
|
543
|
+
function hasMeaningfulKitScreen(screenSource) {
|
|
544
|
+
for (const match of screenSource.matchAll(WIREFRAME_TEXT_ATTR_RE)) {
|
|
545
|
+
if (meaningfulTextLength(match[1] ?? match[2] ?? match[3]) >= 2) {
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (/\bitems\s*=\s*\{[\s\S]*?\blabel\s*:/i.test(screenSource))
|
|
550
|
+
return true;
|
|
551
|
+
if (/\brows\s*=\s*\{[\s\S]*?\b[klv]\s*:/i.test(screenSource))
|
|
552
|
+
return true;
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
function hasMeaningfulWireframeScreen(blockSource) {
|
|
556
|
+
const screenMatch = /<Screen\b/.exec(blockSource);
|
|
557
|
+
if (!screenMatch)
|
|
558
|
+
return false;
|
|
559
|
+
const screenStart = screenMatch.index;
|
|
560
|
+
const screenOpeningEnd = findJsxOpeningTagEnd(blockSource, screenStart);
|
|
561
|
+
if (screenOpeningEnd < 0)
|
|
562
|
+
return false;
|
|
563
|
+
const screenOpening = blockSource.slice(screenStart, screenOpeningEnd + 1);
|
|
564
|
+
const htmlMeaningful = hasMeaningfulWireframeHtml(screenOpening);
|
|
565
|
+
if (htmlMeaningful === true)
|
|
566
|
+
return true;
|
|
567
|
+
const selfClosing = /\/\s*>$/.test(screenOpening);
|
|
568
|
+
const closeIndex = selfClosing
|
|
569
|
+
? -1
|
|
570
|
+
: blockSource.indexOf("</Screen>", screenOpeningEnd + 1);
|
|
571
|
+
const screenSource = closeIndex >= 0
|
|
572
|
+
? blockSource.slice(screenStart, closeIndex + "</Screen>".length)
|
|
573
|
+
: screenOpening;
|
|
574
|
+
if (hasMeaningfulKitScreen(screenSource))
|
|
575
|
+
return true;
|
|
576
|
+
return htmlMeaningful === null ? null : false;
|
|
577
|
+
}
|
|
578
|
+
function lintWireframeBlocks(file, source, issues) {
|
|
579
|
+
const scanSource = maskFencedCode(source);
|
|
580
|
+
const re = /<WireframeBlock\b/g;
|
|
581
|
+
let match;
|
|
582
|
+
while ((match = re.exec(scanSource))) {
|
|
583
|
+
const start = match.index;
|
|
584
|
+
const openingEnd = findJsxOpeningTagEnd(scanSource, start);
|
|
585
|
+
if (openingEnd < 0) {
|
|
586
|
+
addValidationIssue(issues, file, source, start, "WireframeBlock opening tag is not closed.");
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const opening = scanSource.slice(start, openingEnd + 1);
|
|
590
|
+
const unsupportedAttr = opening.match(/\b(data|screens|screen|elements)\s*=/);
|
|
591
|
+
if (unsupportedAttr) {
|
|
592
|
+
addValidationIssue(issues, file, source, start, `WireframeBlock uses unsupported "${unsupportedAttr[1]}" prop. Put content inside a <Screen> child instead.`);
|
|
593
|
+
}
|
|
594
|
+
const selfClosing = /\/\s*>$/.test(opening);
|
|
595
|
+
const closeTag = "</WireframeBlock>";
|
|
596
|
+
const closeIndex = selfClosing
|
|
597
|
+
? -1
|
|
598
|
+
: scanSource.indexOf(closeTag, openingEnd + 1);
|
|
599
|
+
const blockSource = selfClosing
|
|
600
|
+
? opening
|
|
601
|
+
: closeIndex >= 0
|
|
602
|
+
? scanSource.slice(start, closeIndex + closeTag.length)
|
|
603
|
+
: scanSource.slice(start, openingEnd + 1);
|
|
604
|
+
if (!selfClosing && closeIndex < 0) {
|
|
605
|
+
addValidationIssue(issues, file, source, start, "WireframeBlock must have a closing </WireframeBlock> tag.");
|
|
606
|
+
}
|
|
607
|
+
if (selfClosing || !/<Screen\b/.test(blockSource)) {
|
|
608
|
+
addValidationIssue(issues, file, source, start, 'WireframeBlock must wrap a <Screen> child; self-closing wireframes render empty. Use <WireframeBlock><Screen surface="browser">...</Screen></WireframeBlock>.');
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const meaningfulScreen = hasMeaningfulWireframeScreen(blockSource);
|
|
612
|
+
if (meaningfulScreen === false) {
|
|
613
|
+
addValidationIssue(issues, file, source, start, 'WireframeBlock contains an empty <Screen>; local previews render blank wireframes. Add visible html text/controls or kit nodes such as <Title text="Checkout" /> and <Btn label="Pay" />.');
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function lintColumnsBlocks(file, source, issues) {
|
|
618
|
+
const scanSource = maskFencedCode(source);
|
|
619
|
+
const re = /<Columns\b/g;
|
|
620
|
+
let match;
|
|
621
|
+
while ((match = re.exec(scanSource))) {
|
|
622
|
+
const start = match.index;
|
|
623
|
+
const openingEnd = findJsxOpeningTagEnd(scanSource, start);
|
|
624
|
+
if (openingEnd < 0)
|
|
625
|
+
continue;
|
|
626
|
+
const opening = scanSource.slice(start, openingEnd + 1);
|
|
627
|
+
if (/\bcolumns\s*=/.test(opening)) {
|
|
628
|
+
addValidationIssue(issues, file, source, start, 'Columns must use <Column> children, not a columns= prop. Use <Columns><Column label="Before">...</Column><Column label="After">...</Column></Columns>.');
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
// Blank out fenced code blocks and inline code spans (preserving newlines and
|
|
633
|
+
// length) so block-tag linters don't trip on documentation examples written in
|
|
634
|
+
// prose — e.g. an inline `<WireframeBlock><Screen>...</Screen></WireframeBlock>`
|
|
635
|
+
// example is not a real block to validate. Real blocks (outside code) are left
|
|
636
|
+
// intact, so their offsets/line numbers stay correct. Without this the default
|
|
637
|
+
// `plan local init` scaffold fails its own `plan local check`/`serve` lint.
|
|
638
|
+
function maskCodeRegions(source) {
|
|
639
|
+
const blank = (s) => s.replace(/[^\n]/g, " ");
|
|
640
|
+
return source.replace(/```[\s\S]*?```/g, blank).replace(/`[^`\n]*`/g, blank);
|
|
641
|
+
}
|
|
642
|
+
export function validateLocalPlanFiles(files) {
|
|
643
|
+
const issues = [];
|
|
644
|
+
for (const entry of localPlanSourceEntries(files)) {
|
|
645
|
+
const source = maskCodeRegions(entry.source);
|
|
646
|
+
lintWireframeBlocks(entry.file, source, issues);
|
|
647
|
+
lintColumnsBlocks(entry.file, source, issues);
|
|
648
|
+
}
|
|
649
|
+
return issues;
|
|
650
|
+
}
|
|
651
|
+
export function assertLocalPlanFilesValid(files) {
|
|
652
|
+
const issues = validateLocalPlanFiles(files);
|
|
653
|
+
if (issues.length === 0)
|
|
654
|
+
return;
|
|
655
|
+
const details = issues
|
|
656
|
+
.slice(0, 8)
|
|
657
|
+
.map((issue) => `${issue.file}:${issue.line} ${issue.message}`)
|
|
658
|
+
.join("\n");
|
|
659
|
+
const overflow = issues.length > 8 ? `\n...plus ${issues.length - 8} more issues` : "";
|
|
660
|
+
throw new Error(`Local plan source validation failed:\n${details}${overflow}\nRun \`npx @agent-native/core@latest plan blocks --out plan-blocks.md\` and update the MDX to the documented block shapes.`);
|
|
661
|
+
}
|
|
262
662
|
export function buildLocalPlanPreviewHtml(input) {
|
|
263
663
|
const files = readLocalPlanFiles(input.dir);
|
|
664
|
+
assertLocalPlanFilesValid(files);
|
|
264
665
|
const parsed = stripFrontmatter(files.planMdx);
|
|
265
666
|
const title = input.title ||
|
|
266
667
|
parsed.frontmatter.title ||
|
|
@@ -358,7 +759,9 @@ export function buildLocalPlanPreviewHtml(input) {
|
|
|
358
759
|
}
|
|
359
760
|
export function writeLocalPlanPreview(input) {
|
|
360
761
|
const dir = path.resolve(input.dir);
|
|
361
|
-
const
|
|
762
|
+
const files = readLocalPlanFiles(dir);
|
|
763
|
+
assertLocalPlanFilesValid(files);
|
|
764
|
+
const parsed = stripFrontmatter(files.planMdx);
|
|
362
765
|
const kind = input.kind || normalizeKind(parsed.frontmatter.kind);
|
|
363
766
|
const title = input.title ||
|
|
364
767
|
parsed.frontmatter.title ||
|
|
@@ -369,12 +772,6 @@ export function writeLocalPlanPreview(input) {
|
|
|
369
772
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
370
773
|
fs.writeFileSync(out, buildLocalPlanPreviewHtml({ ...input, dir, kind }));
|
|
371
774
|
}
|
|
372
|
-
const files = [
|
|
373
|
-
"plan.mdx",
|
|
374
|
-
"canvas.mdx",
|
|
375
|
-
"prototype.mdx",
|
|
376
|
-
".plan-state.json",
|
|
377
|
-
].filter((file) => fs.existsSync(path.join(dir, file)));
|
|
378
775
|
const result = {
|
|
379
776
|
ok: true,
|
|
380
777
|
dir,
|
|
@@ -382,7 +779,7 @@ export function writeLocalPlanPreview(input) {
|
|
|
382
779
|
url: out ? pathToFileURL(out).href : localPlanPreviewUrl(dir, input.appUrl),
|
|
383
780
|
title,
|
|
384
781
|
kind,
|
|
385
|
-
files,
|
|
782
|
+
files: localPlanFileList(files),
|
|
386
783
|
};
|
|
387
784
|
if (!input.open)
|
|
388
785
|
return result;
|
|
@@ -394,6 +791,162 @@ export function writeLocalPlanPreview(input) {
|
|
|
394
791
|
...(openResult.error ? { openError: openResult.error } : {}),
|
|
395
792
|
};
|
|
396
793
|
}
|
|
794
|
+
function buildLocalPlanBridgePayload(input) {
|
|
795
|
+
const dir = path.resolve(input.dir);
|
|
796
|
+
const files = readLocalPlanFiles(dir);
|
|
797
|
+
assertLocalPlanFilesValid(files);
|
|
798
|
+
const parsed = stripFrontmatter(files.planMdx);
|
|
799
|
+
const kind = input.kind || normalizeKind(parsed.frontmatter.kind);
|
|
800
|
+
const title = input.title ||
|
|
801
|
+
parsed.frontmatter.title ||
|
|
802
|
+
firstHeading(parsed.body) ||
|
|
803
|
+
path.basename(dir);
|
|
804
|
+
const brief = input.brief || parsed.frontmatter.brief || "";
|
|
805
|
+
return {
|
|
806
|
+
ok: true,
|
|
807
|
+
version: 1,
|
|
808
|
+
source: "agent-native-local-bridge",
|
|
809
|
+
localOnly: true,
|
|
810
|
+
slug: path.basename(dir),
|
|
811
|
+
dir,
|
|
812
|
+
title,
|
|
813
|
+
brief,
|
|
814
|
+
kind,
|
|
815
|
+
updatedAt: latestLocalPlanMtime(dir, files),
|
|
816
|
+
files: localPlanFileList(files),
|
|
817
|
+
mdx: localPlanMdxFolder(files),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function latestLocalPlanMtime(dir, files) {
|
|
821
|
+
const candidates = [
|
|
822
|
+
path.join(dir, "plan.mdx"),
|
|
823
|
+
...(files.canvasMdx ? [path.join(dir, "canvas.mdx")] : []),
|
|
824
|
+
...(files.prototypeMdx ? [path.join(dir, "prototype.mdx")] : []),
|
|
825
|
+
...(files.stateJson ? [path.join(dir, ".plan-state.json")] : []),
|
|
826
|
+
...Object.keys(files.assets ?? {}).map((filename) => path.join(dir, "assets", filename)),
|
|
827
|
+
];
|
|
828
|
+
let latest = 0;
|
|
829
|
+
for (const file of candidates) {
|
|
830
|
+
try {
|
|
831
|
+
latest = Math.max(latest, fs.statSync(file).mtimeMs);
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
// Ignore files deleted between the read and stat passes.
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return new Date(latest || Date.now()).toISOString();
|
|
838
|
+
}
|
|
839
|
+
function sendBridgeJson(res, status, payload) {
|
|
840
|
+
res.writeHead(status, {
|
|
841
|
+
"access-control-allow-origin": "*",
|
|
842
|
+
"access-control-allow-methods": "GET, OPTIONS",
|
|
843
|
+
"access-control-allow-headers": "content-type",
|
|
844
|
+
// Required when the hosted HTTPS Plan UI fetches this localhost bridge.
|
|
845
|
+
"access-control-allow-private-network": "true",
|
|
846
|
+
"cache-control": "no-store",
|
|
847
|
+
"content-type": "application/json; charset=utf-8",
|
|
848
|
+
"x-agent-native-local-bridge": "1",
|
|
849
|
+
});
|
|
850
|
+
res.end(`${JSON.stringify(payload)}\n`);
|
|
851
|
+
}
|
|
852
|
+
function bridgeRequestUrl(req) {
|
|
853
|
+
return new URL(req.url || "/", "http://127.0.0.1");
|
|
854
|
+
}
|
|
855
|
+
function bridgeHostForUrl(host) {
|
|
856
|
+
if (host === "0.0.0.0" || host === "::")
|
|
857
|
+
return "127.0.0.1";
|
|
858
|
+
return host;
|
|
859
|
+
}
|
|
860
|
+
export async function startLocalPlanBridge(input) {
|
|
861
|
+
const dir = path.resolve(input.dir);
|
|
862
|
+
const initialPayload = buildLocalPlanBridgePayload({
|
|
863
|
+
dir,
|
|
864
|
+
kind: input.kind,
|
|
865
|
+
title: input.title,
|
|
866
|
+
brief: input.brief,
|
|
867
|
+
});
|
|
868
|
+
const token = input.token || crypto.randomBytes(24).toString("base64url");
|
|
869
|
+
const host = input.host || "127.0.0.1";
|
|
870
|
+
const appUrl = normalizeBridgeAppUrl(input.appUrl);
|
|
871
|
+
const server = http.createServer((req, res) => {
|
|
872
|
+
if (req.method === "OPTIONS") {
|
|
873
|
+
sendBridgeJson(res, 204, "");
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (req.method !== "GET") {
|
|
877
|
+
sendBridgeJson(res, 405, { ok: false, error: "Method not allowed." });
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const url = bridgeRequestUrl(req);
|
|
881
|
+
if (url.pathname !== "/local-plan.json") {
|
|
882
|
+
sendBridgeJson(res, 404, { ok: false, error: "Not found." });
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (url.searchParams.get("token") !== token) {
|
|
886
|
+
sendBridgeJson(res, 403, { ok: false, error: "Invalid bridge token." });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
sendBridgeJson(res, 200, buildLocalPlanBridgePayload({
|
|
891
|
+
dir,
|
|
892
|
+
kind: input.kind,
|
|
893
|
+
title: input.title,
|
|
894
|
+
brief: input.brief,
|
|
895
|
+
}));
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
sendBridgeJson(res, 500, {
|
|
899
|
+
ok: false,
|
|
900
|
+
error: error instanceof Error ? error.message : String(error),
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
await new Promise((resolve, reject) => {
|
|
905
|
+
const onError = (error) => {
|
|
906
|
+
server.off("listening", onListening);
|
|
907
|
+
reject(error);
|
|
908
|
+
};
|
|
909
|
+
const onListening = () => {
|
|
910
|
+
server.off("error", onError);
|
|
911
|
+
resolve();
|
|
912
|
+
};
|
|
913
|
+
server.once("error", onError);
|
|
914
|
+
server.once("listening", onListening);
|
|
915
|
+
server.listen(input.port ?? 0, host);
|
|
916
|
+
});
|
|
917
|
+
const address = server.address();
|
|
918
|
+
if (!address || typeof address === "string") {
|
|
919
|
+
server.close();
|
|
920
|
+
throw new Error("Local plan bridge did not bind to a TCP port.");
|
|
921
|
+
}
|
|
922
|
+
const bridgeUrl = `http://${bridgeHostForUrl(host)}:${address.port}/local-plan.json?token=${encodeURIComponent(token)}`;
|
|
923
|
+
const url = localPlanBridgePageUrl({ dir, bridgeUrl, appUrl });
|
|
924
|
+
const openResult = input.open
|
|
925
|
+
? (input.openUrl || openLocalUrl)(url)
|
|
926
|
+
: undefined;
|
|
927
|
+
return {
|
|
928
|
+
server,
|
|
929
|
+
result: {
|
|
930
|
+
ok: true,
|
|
931
|
+
dir,
|
|
932
|
+
url,
|
|
933
|
+
bridgeUrl,
|
|
934
|
+
appUrl,
|
|
935
|
+
title: initialPayload.title,
|
|
936
|
+
kind: initialPayload.kind,
|
|
937
|
+
files: initialPayload.files,
|
|
938
|
+
host,
|
|
939
|
+
port: address.port,
|
|
940
|
+
...(openResult
|
|
941
|
+
? {
|
|
942
|
+
opened: openResult.ok,
|
|
943
|
+
openCommand: openResult.command,
|
|
944
|
+
...(openResult.error ? { openError: openResult.error } : {}),
|
|
945
|
+
}
|
|
946
|
+
: {}),
|
|
947
|
+
},
|
|
948
|
+
};
|
|
949
|
+
}
|
|
397
950
|
function writeLocalPlanSkeleton(input) {
|
|
398
951
|
const dir = path.resolve(input.dir || path.join(defaultPlansDir(), localPlanFolderName(input.title)));
|
|
399
952
|
const planPath = path.join(dir, "plan.mdx");
|
|
@@ -421,9 +974,9 @@ function writeLocalPlanSkeleton(input) {
|
|
|
421
974
|
"## Review Surface",
|
|
422
975
|
"",
|
|
423
976
|
"Author the structured plan or recap here. You can add Agent-Native Plan MDX",
|
|
424
|
-
|
|
425
|
-
"`<FileTree />`, or `<Diff />`; the local
|
|
426
|
-
"without publishing it to the Plan app.",
|
|
977
|
+
'blocks such as `<WireframeBlock><Screen surface="browser">...</Screen></WireframeBlock>`,',
|
|
978
|
+
"`<Diagram />`, `<TabsBlock />`, `<FileTree />`, or `<Diff />`; the local",
|
|
979
|
+
"preview will show the source without publishing it to the Plan app.",
|
|
427
980
|
"",
|
|
428
981
|
].join("\n");
|
|
429
982
|
fs.writeFileSync(planPath, mdx, "utf-8");
|
|
@@ -448,10 +1001,12 @@ function runInit(args) {
|
|
|
448
1001
|
function runCheck(args) {
|
|
449
1002
|
const dir = stringArg(args, "dir");
|
|
450
1003
|
const files = readLocalPlanFiles(dir);
|
|
1004
|
+
assertLocalPlanFilesValid(files);
|
|
451
1005
|
const parsed = stripFrontmatter(files.planMdx);
|
|
452
1006
|
const result = {
|
|
453
1007
|
ok: true,
|
|
454
1008
|
noDb: true,
|
|
1009
|
+
validation: "passed",
|
|
455
1010
|
dir: files.dir,
|
|
456
1011
|
title: parsed.frontmatter.title || firstHeading(parsed.body),
|
|
457
1012
|
kind: normalizeKind(parsed.frontmatter.kind),
|
|
@@ -466,6 +1021,12 @@ function runCheck(args) {
|
|
|
466
1021
|
...(files.stateJson
|
|
467
1022
|
? { ".plan-state.json": Buffer.byteLength(files.stateJson) }
|
|
468
1023
|
: {}),
|
|
1024
|
+
...(files.assets
|
|
1025
|
+
? Object.fromEntries(Object.entries(files.assets).map(([filename, base64]) => [
|
|
1026
|
+
`assets/${filename}`,
|
|
1027
|
+
Buffer.byteLength(base64, "base64"),
|
|
1028
|
+
]))
|
|
1029
|
+
: {}),
|
|
469
1030
|
},
|
|
470
1031
|
};
|
|
471
1032
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
@@ -484,6 +1045,34 @@ function runPreview(args) {
|
|
|
484
1045
|
});
|
|
485
1046
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
486
1047
|
}
|
|
1048
|
+
async function runServe(args) {
|
|
1049
|
+
const portValue = optionalArg(args, "port");
|
|
1050
|
+
const port = portValue ? Number(portValue) : undefined;
|
|
1051
|
+
if (portValue && (!Number.isInteger(port) || port < 0 || port > 65535)) {
|
|
1052
|
+
throw new Error("--port must be an integer between 0 and 65535.");
|
|
1053
|
+
}
|
|
1054
|
+
const bridge = await startLocalPlanBridge({
|
|
1055
|
+
dir: stringArg(args, "dir"),
|
|
1056
|
+
appUrl: optionalArg(args, "app-url"),
|
|
1057
|
+
title: optionalArg(args, "title"),
|
|
1058
|
+
brief: optionalArg(args, "brief"),
|
|
1059
|
+
host: optionalArg(args, "host"),
|
|
1060
|
+
port,
|
|
1061
|
+
open: boolArg(args, "open"),
|
|
1062
|
+
kind: optionalArg(args, "kind")
|
|
1063
|
+
? normalizeKind(optionalArg(args, "kind"))
|
|
1064
|
+
: undefined,
|
|
1065
|
+
});
|
|
1066
|
+
process.stdout.write(`${JSON.stringify(bridge.result, null, 2)}\n`);
|
|
1067
|
+
process.stderr.write(`Local Plan bridge running at ${bridge.result.bridgeUrl}\nPress Ctrl+C to stop.\n`);
|
|
1068
|
+
await new Promise((resolve) => {
|
|
1069
|
+
const stop = () => {
|
|
1070
|
+
bridge.server.close(() => resolve());
|
|
1071
|
+
};
|
|
1072
|
+
process.once("SIGINT", stop);
|
|
1073
|
+
process.once("SIGTERM", stop);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
487
1076
|
async function runBlocks(args) {
|
|
488
1077
|
const format = normalizePlanBlockFormat(optionalArg(args, "format"));
|
|
489
1078
|
const appUrl = optionalArg(args, "app-url") ||
|
|
@@ -536,6 +1125,7 @@ Usage:
|
|
|
536
1125
|
agent-native plan blocks [--format reference|schema] [--app-url <url>] [--out <file>] [--json]
|
|
537
1126
|
agent-native plan local init --title <title> [--brief <text>] [--kind plan|recap] [--dir <folder>] [--force]
|
|
538
1127
|
agent-native plan local check --dir <folder>
|
|
1128
|
+
agent-native plan local serve --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--port <port>]
|
|
539
1129
|
agent-native plan local preview --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--out preview.html]
|
|
540
1130
|
|
|
541
1131
|
The blocks command fetches the no-auth, read-only get-plan-blocks catalog from
|
|
@@ -552,10 +1142,12 @@ write actions, hosted storage, or SQLite.
|
|
|
552
1142
|
Common flow:
|
|
553
1143
|
agent-native plan blocks --out plan-blocks.md
|
|
554
1144
|
agent-native plan local init --title "Checkout review" --kind plan
|
|
555
|
-
agent-native plan local
|
|
1145
|
+
agent-native plan local serve --dir plans/checkout-review --open
|
|
556
1146
|
|
|
557
|
-
\`plan local
|
|
558
|
-
|
|
1147
|
+
\`plan local serve\` starts a tiny localhost bridge and opens the hosted Plan UI
|
|
1148
|
+
against that local-only source. The hosted app fetches the MDX from localhost in
|
|
1149
|
+
the browser; it does not write plan content to the hosted database. Use
|
|
1150
|
+
\`plan local preview\` for a local Plan dev server route. \`preview --out\` is a
|
|
559
1151
|
legacy/debug escape hatch that writes a standalone static HTML file.
|
|
560
1152
|
`;
|
|
561
1153
|
export async function runPlan(argv) {
|
|
@@ -582,6 +1174,12 @@ export async function runPlan(argv) {
|
|
|
582
1174
|
process.exit(1);
|
|
583
1175
|
}
|
|
584
1176
|
const args = parseArgs(rest);
|
|
1177
|
+
// `plan local <sub> --help` / `-h` shows help instead of running the
|
|
1178
|
+
// subcommand (e.g. `plan local init --help` must not scaffold a folder).
|
|
1179
|
+
if (args.help === true || args.h === true) {
|
|
1180
|
+
process.stdout.write(HELP);
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
585
1183
|
switch (sub) {
|
|
586
1184
|
case "init":
|
|
587
1185
|
runInit(args);
|
|
@@ -592,6 +1190,9 @@ export async function runPlan(argv) {
|
|
|
592
1190
|
case "preview":
|
|
593
1191
|
runPreview(args);
|
|
594
1192
|
return;
|
|
1193
|
+
case "serve":
|
|
1194
|
+
await runServe(args);
|
|
1195
|
+
return;
|
|
595
1196
|
case "help":
|
|
596
1197
|
case "--help":
|
|
597
1198
|
case "-h":
|