@sponsio/sdk 0.1.0-alpha.3 → 0.2.0-alpha.4
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/bin/sponsio.cjs +11 -0
- package/contracts/benchmark/agentdojo.yaml +551 -0
- package/contracts/benchmark/odcv_bench.yaml +393 -0
- package/contracts/benchmark/redcode_exec.yaml +488 -0
- package/contracts/benchmark/swebench.yaml +176 -0
- package/contracts/benchmark/tau2_bench.yaml +984 -0
- package/contracts/capability/credentials.yaml +8 -8
- package/contracts/capability/database.yaml +6 -6
- package/contracts/capability/filesystem-strict.yaml +4 -4
- package/contracts/capability/filesystem.yaml +9 -9
- package/contracts/capability/host-config-integrity.yaml +285 -0
- package/contracts/capability/self-modify.yaml +214 -9
- package/contracts/capability/shell.yaml +25 -32
- package/contracts/capability/subagent.yaml +5 -5
- package/contracts/core/llm_safety.yaml +21 -13
- package/contracts/incident/claude-code-secret-bypass.yaml +11 -12
- package/contracts/incident/cursor-railway-wipe.yaml +3 -3
- package/contracts/incident/mcp-composition.yaml +12 -12
- package/contracts/incident/openclaw.yaml +54 -45
- package/contracts/incident/subagent-escape.yaml +4 -4
- package/contracts/premium/.gitkeep +5 -6
- package/dist/cli/check.d.ts +2 -0
- package/dist/cli/check.d.ts.map +1 -0
- package/dist/cli/check.js +283 -0
- package/dist/cli/check.js.map +1 -0
- package/dist/cli/cli.d.ts +25 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +459 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/config.d.ts +88 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +243 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/demo.d.ts +12 -0
- package/dist/cli/demo.d.ts.map +1 -0
- package/dist/cli/demo.js +350 -0
- package/dist/cli/demo.js.map +1 -0
- package/dist/cli/doctor.d.ts +11 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +201 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/eval.d.ts +2 -0
- package/dist/cli/eval.d.ts.map +1 -0
- package/dist/cli/eval.js +361 -0
- package/dist/cli/eval.js.map +1 -0
- package/dist/cli/explain.d.ts +2 -0
- package/dist/cli/explain.d.ts.map +1 -0
- package/dist/cli/explain.js +266 -0
- package/dist/cli/explain.js.map +1 -0
- package/dist/cli/export-sessions.d.ts +2 -0
- package/dist/cli/export-sessions.d.ts.map +1 -0
- package/dist/cli/export-sessions.js +182 -0
- package/dist/cli/export-sessions.js.map +1 -0
- package/dist/cli/export.d.ts +2 -0
- package/dist/cli/export.d.ts.map +1 -0
- package/dist/cli/export.js +222 -0
- package/dist/cli/export.js.map +1 -0
- package/dist/cli/extractors/common.d.ts +27 -0
- package/dist/cli/extractors/common.d.ts.map +1 -0
- package/dist/cli/extractors/common.js +107 -0
- package/dist/cli/extractors/common.js.map +1 -0
- package/dist/cli/extractors/generic.d.ts +37 -0
- package/dist/cli/extractors/generic.d.ts.map +1 -0
- package/dist/cli/extractors/generic.js +185 -0
- package/dist/cli/extractors/generic.js.map +1 -0
- package/dist/cli/extractors/langchain.d.ts +18 -0
- package/dist/cli/extractors/langchain.d.ts.map +1 -0
- package/dist/cli/extractors/langchain.js +86 -0
- package/dist/cli/extractors/langchain.js.map +1 -0
- package/dist/cli/extractors/vercel.d.ts +17 -0
- package/dist/cli/extractors/vercel.d.ts.map +1 -0
- package/dist/cli/extractors/vercel.js +46 -0
- package/dist/cli/extractors/vercel.js.map +1 -0
- package/dist/cli/index.d.ts +38 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +146 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +177 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/mode.d.ts +2 -0
- package/dist/cli/mode.d.ts.map +1 -0
- package/dist/cli/mode.js +71 -0
- package/dist/cli/mode.js.map +1 -0
- package/dist/cli/onboard.d.ts +88 -0
- package/dist/cli/onboard.d.ts.map +1 -0
- package/dist/cli/onboard.js +758 -0
- package/dist/cli/onboard.js.map +1 -0
- package/dist/cli/otlp.d.ts +27 -0
- package/dist/cli/otlp.d.ts.map +1 -0
- package/dist/cli/otlp.js +79 -0
- package/dist/cli/otlp.js.map +1 -0
- package/dist/cli/package.json +1 -0
- package/dist/cli/packs.d.ts +16 -0
- package/dist/cli/packs.d.ts.map +1 -0
- package/dist/cli/packs.js +184 -0
- package/dist/cli/packs.js.map +1 -0
- package/dist/cli/patterns.d.ts +16 -0
- package/dist/cli/patterns.d.ts.map +1 -0
- package/dist/cli/patterns.js +136 -0
- package/dist/cli/patterns.js.map +1 -0
- package/dist/cli/prompt.d.ts +2 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +54 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/replay.d.ts +2 -0
- package/dist/cli/replay.d.ts.map +1 -0
- package/dist/cli/replay.js +196 -0
- package/dist/cli/replay.js.map +1 -0
- package/dist/cli/report.d.ts +57 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +298 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/scan.d.ts +2 -0
- package/dist/cli/scan.d.ts.map +1 -0
- package/dist/cli/scan.js +172 -0
- package/dist/cli/scan.js.map +1 -0
- package/dist/cli/skill.d.ts +15 -0
- package/dist/cli/skill.d.ts.map +1 -0
- package/dist/cli/skill.js +131 -0
- package/dist/cli/skill.js.map +1 -0
- package/dist/cli/tokens.d.ts +31 -0
- package/dist/cli/tokens.d.ts.map +1 -0
- package/dist/cli/tokens.js +73 -0
- package/dist/cli/tokens.js.map +1 -0
- package/dist/cli/types.d.ts +46 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +3 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/validate.d.ts +19 -0
- package/dist/cli/validate.d.ts.map +1 -0
- package/dist/cli/validate.js +205 -0
- package/dist/cli/validate.js.map +1 -0
- package/dist/cli/zod.d.ts +36 -0
- package/dist/cli/zod.d.ts.map +1 -0
- package/dist/cli/zod.js +226 -0
- package/dist/cli/zod.js.map +1 -0
- package/dist/contract.d.ts +13 -13
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +21 -21
- package/dist/contract.js.map +1 -1
- package/dist/core/config-loader.d.ts +1 -1
- package/dist/core/config-loader.d.ts.map +1 -1
- package/dist/core/config-loader.js +100 -26
- package/dist/core/config-loader.js.map +1 -1
- package/dist/core/evaluator.d.ts +9 -1
- package/dist/core/evaluator.d.ts.map +1 -1
- package/dist/core/evaluator.js +189 -13
- package/dist/core/evaluator.js.map +1 -1
- package/dist/core/formula.d.ts +76 -17
- package/dist/core/formula.d.ts.map +1 -1
- package/dist/core/formula.js +83 -1
- package/dist/core/formula.js.map +1 -1
- package/dist/core/grounding.d.ts +50 -3
- package/dist/core/grounding.d.ts.map +1 -1
- package/dist/core/grounding.js +317 -141
- package/dist/core/grounding.js.map +1 -1
- package/dist/core/nl-parser.d.ts +1 -1
- package/dist/core/nl-parser.d.ts.map +1 -1
- package/dist/core/nl-parser.js +76 -4
- package/dist/core/nl-parser.js.map +1 -1
- package/dist/core/pack-loader.d.ts.map +1 -1
- package/dist/core/pack-loader.js +14 -2
- package/dist/core/pack-loader.js.map +1 -1
- package/dist/core/parser.d.ts.map +1 -1
- package/dist/core/parser.js +30 -1
- package/dist/core/parser.js.map +1 -1
- package/dist/core/pattern-factory.d.ts +4 -4
- package/dist/core/pattern-factory.d.ts.map +1 -1
- package/dist/core/pattern-factory.js +53 -4
- package/dist/core/pattern-factory.js.map +1 -1
- package/dist/core/patterns.d.ts +127 -5
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js +267 -3
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/session-log.d.ts.map +1 -1
- package/dist/core/session-log.js +20 -1
- package/dist/core/session-log.js.map +1 -1
- package/dist/core/spans.d.ts +1 -1
- package/dist/core/spans.d.ts.map +1 -1
- package/dist/core/spans.js.map +1 -1
- package/dist/core/sto.d.ts +30 -66
- package/dist/core/sto.d.ts.map +1 -1
- package/dist/core/sto.js +39 -100
- package/dist/core/sto.js.map +1 -1
- package/dist/index.d.ts +83 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -142
- package/dist/index.js.map +1 -1
- package/dist/integrations/openai-agents.d.ts.map +1 -1
- package/dist/integrations/openai-agents.js +6 -4
- package/dist/integrations/openai-agents.js.map +1 -1
- package/dist/integrations/vercel-ai.d.ts.map +1 -1
- package/dist/integrations/vercel-ai.js +21 -3
- package/dist/integrations/vercel-ai.js.map +1 -1
- package/dist/render/tokens.d.ts.map +1 -1
- package/dist/render/tokens.js +1 -0
- package/dist/render/tokens.js.map +1 -1
- package/package.json +35 -7
- package/prompts/onboard.md +246 -0
- package/prompts/scan.md +123 -0
- package/skills/SKILL.md +432 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ``sponsio onboard`` — TypeScript-first onboarding for
|
|
4
|
+
* Node.js agent codebases.
|
|
5
|
+
*
|
|
6
|
+
* 1. Static-scan the tree for tool definitions (same engine as
|
|
7
|
+
* ``scan``).
|
|
8
|
+
* 2. Write a temp ``.json`` inventory and, when the Python ``sponsio``
|
|
9
|
+
* CLI is on ``PATH``, run
|
|
10
|
+
* ``sponsio scan <inventory.json> -o <sponsio.yaml>`` for the
|
|
11
|
+
* same inference / starter-pack / pack behaviour as
|
|
12
|
+
* ``sponsio onboard .`` in a Python project.
|
|
13
|
+
* 3. If ``sponsio`` is missing, write a *minimal* ``sponsio.yaml``
|
|
14
|
+
* (observe mode + name-heuristic NL ``E:`` strings that the TS
|
|
15
|
+
* runtime can parse) so ``new Sponsio({ config: ... })`` works
|
|
16
|
+
* without a Python install.
|
|
17
|
+
*
|
|
18
|
+
* The TS SDK has no LTL / pack / sto runtime — the conservative NL
|
|
19
|
+
* fallback is intentionally det-only. Users who install ``sponsio``
|
|
20
|
+
* get the full Python output (including includes / structured rules)
|
|
21
|
+
* and the same file still loads in TS (unsupported entries are
|
|
22
|
+
* skipped with a one-time warning in ``@sponsio/sdk``).
|
|
23
|
+
*/
|
|
24
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
+
}
|
|
30
|
+
Object.defineProperty(o, k2, desc);
|
|
31
|
+
}) : (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
o[k2] = m[k];
|
|
34
|
+
}));
|
|
35
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
+
}) : function(o, v) {
|
|
38
|
+
o["default"] = v;
|
|
39
|
+
});
|
|
40
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
41
|
+
var ownKeys = function(o) {
|
|
42
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
43
|
+
var ar = [];
|
|
44
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
45
|
+
return ar;
|
|
46
|
+
};
|
|
47
|
+
return ownKeys(o);
|
|
48
|
+
};
|
|
49
|
+
return function (mod) {
|
|
50
|
+
if (mod && mod.__esModule) return mod;
|
|
51
|
+
var result = {};
|
|
52
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
53
|
+
__setModuleDefault(result, mod);
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
})();
|
|
57
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
58
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
59
|
+
};
|
|
60
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
61
|
+
exports.suggestDetNlContracts = suggestDetNlContracts;
|
|
62
|
+
exports.detectFramework = detectFramework;
|
|
63
|
+
exports.buildFallbackPayload = buildFallbackPayload;
|
|
64
|
+
exports.runOnboard = runOnboard;
|
|
65
|
+
exports.runOnboardCli = runOnboardCli;
|
|
66
|
+
const fs_1 = require("fs");
|
|
67
|
+
const os_1 = require("os");
|
|
68
|
+
const path_1 = require("path");
|
|
69
|
+
const child_process_1 = require("child_process");
|
|
70
|
+
const js_yaml_1 = require("js-yaml");
|
|
71
|
+
const fast_glob_1 = __importDefault(require("fast-glob"));
|
|
72
|
+
const index_1 = require("./index");
|
|
73
|
+
const tokens_1 = require("./tokens");
|
|
74
|
+
/* ----------------------------------------------------------------- */
|
|
75
|
+
const IRREV_RE = /delete|drop|wipe|purge|destroy|deploy|refund|execute|shell|subprocess|sql|send_email|issue_refund|transfer|approve_payment/i;
|
|
76
|
+
/**
|
|
77
|
+
* Suggest 1–3 deterministic ``E:`` strings that the TS ``parseNl`` can
|
|
78
|
+
* compile — conservative defaults when the Python CLI is missing.
|
|
79
|
+
*/
|
|
80
|
+
function suggestDetNlContracts(names) {
|
|
81
|
+
const nset = new Set(names);
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const n of names) {
|
|
84
|
+
if (IRREV_RE.test(n)) {
|
|
85
|
+
if (nset.has("confirm_with_user") || nset.has("check_policy") || nset.has("verify")) {
|
|
86
|
+
const gate = nset.has("confirm_with_user")
|
|
87
|
+
? "confirm_with_user"
|
|
88
|
+
: nset.has("check_policy")
|
|
89
|
+
? "check_policy"
|
|
90
|
+
: "verify";
|
|
91
|
+
out.push({ E: `tool \`${gate}\` must precede \`${n}\`` });
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
out.push({ E: `tool \`${n}\` at most once` });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (out.length >= 3)
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (out.length === 0) {
|
|
101
|
+
const c = names.find((x) => /send|email|message|post|notify|webhook|slack|sms/i.test(x));
|
|
102
|
+
if (c)
|
|
103
|
+
out.push({ E: `tool \`${c}\` at most 5 times` });
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Heuristic framework id from the nearest ``package.json`` in
|
|
109
|
+
* ``root`` — used for the printed TypeScript wrap snippet only.
|
|
110
|
+
*/
|
|
111
|
+
function detectFramework(root) {
|
|
112
|
+
const pkgPath = (0, path_1.join)(root, "package.json");
|
|
113
|
+
if (!(0, fs_1.existsSync)(pkgPath))
|
|
114
|
+
return "none";
|
|
115
|
+
let raw;
|
|
116
|
+
try {
|
|
117
|
+
raw = JSON.parse((0, fs_1.readFileSync)(pkgPath, "utf-8"));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return "none";
|
|
121
|
+
}
|
|
122
|
+
const d = { ...raw.dependencies, ...raw.devDependencies };
|
|
123
|
+
if (d["@modelcontextprotocol/sdk"])
|
|
124
|
+
return "mcp";
|
|
125
|
+
if (d["@langchain/langgraph"] || d["@langchain/core"])
|
|
126
|
+
return "langgraph";
|
|
127
|
+
if (d["@anthropic-ai/claude-agent-sdk"] || d["claude-agent-sdk"])
|
|
128
|
+
return "claude";
|
|
129
|
+
if (d["@google/adk"])
|
|
130
|
+
return "google_adk";
|
|
131
|
+
if (d["ai"] || d["@ai-sdk/openai"])
|
|
132
|
+
return "vercel";
|
|
133
|
+
if (d["openai"])
|
|
134
|
+
return "openai";
|
|
135
|
+
return "none";
|
|
136
|
+
}
|
|
137
|
+
function wrapSnippetFor(framework, agent, configRel) {
|
|
138
|
+
const cfg = JSON.stringify(configRel);
|
|
139
|
+
const ag = JSON.stringify(agent);
|
|
140
|
+
switch (framework) {
|
|
141
|
+
case "langgraph":
|
|
142
|
+
case "mcp":
|
|
143
|
+
return [
|
|
144
|
+
`import { Sponsio } from "@sponsio/sdk";`,
|
|
145
|
+
`import { wrapTools } from "@sponsio/sdk/langchain";`,
|
|
146
|
+
`import { ToolNode } from "@langchain/langgraph/prebuilt";`,
|
|
147
|
+
``,
|
|
148
|
+
`const guard = new Sponsio({ config: ${cfg}, agentId: ${ag} });`,
|
|
149
|
+
`const toolNode = new ToolNode(wrapTools(tools, guard));`,
|
|
150
|
+
].join("\n");
|
|
151
|
+
case "claude":
|
|
152
|
+
return [
|
|
153
|
+
`import { ClaudeSDKClient } from "claude-agent-sdk";`,
|
|
154
|
+
`import { Sponsio } from "@sponsio/sdk";`,
|
|
155
|
+
`import { sponsioHooks } from "@sponsio/sdk/claude-agent";`,
|
|
156
|
+
``,
|
|
157
|
+
`const guard = new Sponsio({ config: ${cfg}, agentId: ${ag} });`,
|
|
158
|
+
`const client = new ClaudeSDKClient({ hooks: sponsioHooks(guard) });`,
|
|
159
|
+
].join("\n");
|
|
160
|
+
case "google_adk":
|
|
161
|
+
return [
|
|
162
|
+
`import { Sponsio } from "@sponsio/sdk";`,
|
|
163
|
+
`import { wrapGoogleAdkTools } from "@sponsio/sdk/google-adk";`,
|
|
164
|
+
``,
|
|
165
|
+
`const guard = new Sponsio({ config: ${cfg}, agentId: ${ag} });`,
|
|
166
|
+
`const guardedTools = wrapGoogleAdkTools(tools, guard);`,
|
|
167
|
+
`// pass guardedTools to new LlmAgent({ tools: guardedTools, ... })`,
|
|
168
|
+
].join("\n");
|
|
169
|
+
case "none":
|
|
170
|
+
return [
|
|
171
|
+
`import { Sponsio } from "@sponsio/sdk";`,
|
|
172
|
+
``,
|
|
173
|
+
`const guard = new Sponsio({ config: ${cfg}, agentId: ${ag} });`,
|
|
174
|
+
``,
|
|
175
|
+
`// Wrap your tool loop:`,
|
|
176
|
+
`const check = guard.guardBefore(toolName, toolArgs);`,
|
|
177
|
+
`if (check.allowed) {`,
|
|
178
|
+
` const output = await runTool(toolName, toolArgs);`,
|
|
179
|
+
` await guard.guardAfter(toolName, output);`,
|
|
180
|
+
`} else {`,
|
|
181
|
+
` // feed check.detViolations[0].message back to the model`,
|
|
182
|
+
`}`,
|
|
183
|
+
].join("\n");
|
|
184
|
+
case "vercel":
|
|
185
|
+
return [
|
|
186
|
+
`import { Sponsio } from "@sponsio/sdk";`,
|
|
187
|
+
`import { sponsioMiddleware } from "@sponsio/sdk/vercel-ai";`,
|
|
188
|
+
`import { wrapLanguageModel } from "ai";`,
|
|
189
|
+
``,
|
|
190
|
+
`const guard = new Sponsio({ config: ${cfg}, agentId: ${ag} });`,
|
|
191
|
+
`// const model = wrapLanguageModel({ model, middleware: sponsioMiddleware(guard) })`,
|
|
192
|
+
].join("\n");
|
|
193
|
+
case "openai":
|
|
194
|
+
return [
|
|
195
|
+
`import OpenAI from "openai";`,
|
|
196
|
+
`import { Sponsio } from "@sponsio/sdk";`,
|
|
197
|
+
`import { wrapOpenAI } from "@sponsio/sdk/openai";`,
|
|
198
|
+
``,
|
|
199
|
+
`const guard = new Sponsio({ config: ${cfg}, agentId: ${ag} });`,
|
|
200
|
+
`const client = wrapOpenAI(new OpenAI(), guard);`,
|
|
201
|
+
].join("\n");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function resolveOnboardPaths(targetArg) {
|
|
205
|
+
const abs = (0, path_1.resolve)(targetArg);
|
|
206
|
+
if (abs.endsWith(".yaml") || abs.endsWith(".yml")) {
|
|
207
|
+
return {
|
|
208
|
+
root: (0, path_1.dirname)(abs),
|
|
209
|
+
outPath: abs,
|
|
210
|
+
configRelForSnippet: "sponsio.yaml",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
root: abs,
|
|
215
|
+
outPath: (0, path_1.join)(abs, "sponsio.yaml"),
|
|
216
|
+
configRelForSnippet: "sponsio.yaml",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function defaultScanGlobs(root) {
|
|
220
|
+
if ((0, fs_1.existsSync)((0, path_1.join)(root, "src"))) {
|
|
221
|
+
return [
|
|
222
|
+
"src/**/*.ts",
|
|
223
|
+
"src/**/*.tsx",
|
|
224
|
+
"src/**/*.js",
|
|
225
|
+
"src/**/*.jsx",
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
return ["**/*.{ts,tsx,js,jsx}"];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Assemble a minimal, TS-runtime-friendly ``sponsio.yaml`` when the
|
|
232
|
+
* ``sponsio`` CLI is unavailable.
|
|
233
|
+
*/
|
|
234
|
+
function buildFallbackPayload(toolNames, inv, agent, mode) {
|
|
235
|
+
const contracts = suggestDetNlContracts(toolNames);
|
|
236
|
+
return {
|
|
237
|
+
version: 1,
|
|
238
|
+
runtime: { mode },
|
|
239
|
+
tools: inv.tools ?? [],
|
|
240
|
+
agents: {
|
|
241
|
+
[agent]: { contracts: contracts.length ? contracts : [] },
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The full ``onboard`` operation — used by the CLI and (optionally)
|
|
247
|
+
* library callers.
|
|
248
|
+
*/
|
|
249
|
+
async function runOnboard(opts) {
|
|
250
|
+
const { root, outPath, configRelForSnippet } = resolveOnboardPaths(opts.target);
|
|
251
|
+
if (!(0, fs_1.existsSync)(root)) {
|
|
252
|
+
throw new Error(`[onboard] not a path: ${root}`);
|
|
253
|
+
}
|
|
254
|
+
if ((0, fs_1.existsSync)(outPath) && !opts.force) {
|
|
255
|
+
throw new Error(`[onboard] ${outPath} already exists. Pass --force to overwrite, or delete the file.`);
|
|
256
|
+
}
|
|
257
|
+
if (opts.force && (0, fs_1.existsSync)(outPath)) {
|
|
258
|
+
try {
|
|
259
|
+
(0, fs_1.unlinkSync)(outPath);
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
const m = e instanceof Error ? e.message : String(e);
|
|
263
|
+
throw new Error(`[onboard] cannot remove ${outPath}: ${m}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const patterns = defaultScanGlobs(root);
|
|
267
|
+
const so = await (0, index_1.scan)(patterns, { cwd: root });
|
|
268
|
+
const toolNames = so.tools.map((t) => t.function.name);
|
|
269
|
+
const invJson = { tools: so.tools };
|
|
270
|
+
if (so.diagnostics.length) {
|
|
271
|
+
for (const d of so.diagnostics) {
|
|
272
|
+
process.stderr.write(`[${d.level}] ${d.filepath}:${d.line} ${d.message}\n`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const absTmp = (0, path_1.join)((0, os_1.tmpdir)(), `sponsio-onboard-${process.pid}-${Date.now()}.json`);
|
|
276
|
+
(0, fs_1.writeFileSync)(absTmp, JSON.stringify(invJson) + "\n", "utf-8");
|
|
277
|
+
let method = "fallback";
|
|
278
|
+
let message = "";
|
|
279
|
+
if (!opts.pyNever) {
|
|
280
|
+
const args = [
|
|
281
|
+
"scan",
|
|
282
|
+
absTmp,
|
|
283
|
+
"-o",
|
|
284
|
+
outPath,
|
|
285
|
+
"-a",
|
|
286
|
+
opts.agent,
|
|
287
|
+
];
|
|
288
|
+
if (opts.llm)
|
|
289
|
+
args.push("--llm");
|
|
290
|
+
if (opts.push) {
|
|
291
|
+
args.push("--push");
|
|
292
|
+
if (opts.pushUrl)
|
|
293
|
+
args.push("--push-url", opts.pushUrl);
|
|
294
|
+
}
|
|
295
|
+
// Run from the project root so default ``-o sponsio.yaml``-relative
|
|
296
|
+
// paths line up. We pass an *absolute* -o, so CWD is only for any
|
|
297
|
+
// relative path inside the scan engine; inventory is a temp file
|
|
298
|
+
// with no relative deps.
|
|
299
|
+
//
|
|
300
|
+
// Strip ``node_modules/.bin`` from PATH for the spawn — when this
|
|
301
|
+
// CLI was invoked via ``npx sponsio onboard``, PATH starts with
|
|
302
|
+
// node_modules/.bin/ where the TS bin lives. Bare ``sponsio``
|
|
303
|
+
// would then resolve to THIS binary (self-recursion: the JSON
|
|
304
|
+
// tool inventory we just wrote gets re-interpreted as a tool-
|
|
305
|
+
// glob pattern by ``runScanCli``, returning 0 tools and writing
|
|
306
|
+
// an empty yaml). Excluding node_modules forces resolution
|
|
307
|
+
// through the user's broader PATH where the Python ``sponsio``
|
|
308
|
+
// CLI lives.
|
|
309
|
+
const pythonOnlyPath = (process.env.PATH ?? "")
|
|
310
|
+
.split(":")
|
|
311
|
+
.filter((p) => p && !p.includes("node_modules/.bin"))
|
|
312
|
+
.join(":");
|
|
313
|
+
const r = (0, child_process_1.spawnSync)("sponsio", args, {
|
|
314
|
+
encoding: "utf-8",
|
|
315
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
316
|
+
cwd: root,
|
|
317
|
+
env: { ...process.env, PATH: pythonOnlyPath },
|
|
318
|
+
});
|
|
319
|
+
if (r.error && r.error.code === "ENOENT") {
|
|
320
|
+
method = "fallback";
|
|
321
|
+
message =
|
|
322
|
+
"sponsio (Python CLI) not on PATH — wrote a minimal det-only sponsio.yaml. " +
|
|
323
|
+
"Install: pip install sponsio, then re-run for the same output as in a Python repo (packs, LLM, …).";
|
|
324
|
+
}
|
|
325
|
+
else if (r.status === 0) {
|
|
326
|
+
// Indent forwarded subprocess output col-2 so it sits inside
|
|
327
|
+
// the wizard's body margin (banner col-0, body col-2). Without
|
|
328
|
+
// this, Python ``sponsio scan``'s ``✓ Wrote …`` summary +
|
|
329
|
+
// ``· AST scan`` bullets jutted out at col-0 while the TS
|
|
330
|
+
// recap above and below them was col-2. Operates per-line so
|
|
331
|
+
// multi-line stderr / stdout chunks render correctly.
|
|
332
|
+
const indent = (s) => s
|
|
333
|
+
.split("\n")
|
|
334
|
+
.map((ln) => (ln.length > 0 ? " " + ln : ln))
|
|
335
|
+
.join("\n");
|
|
336
|
+
if (r.stderr)
|
|
337
|
+
process.stderr.write(indent(r.stderr));
|
|
338
|
+
if (r.stdout)
|
|
339
|
+
process.stdout.write(indent(r.stdout));
|
|
340
|
+
method = "python";
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
const errMsg = r.error?.message ?? r.stderr?.slice(0, 2000) ?? "";
|
|
344
|
+
if (r.stderr)
|
|
345
|
+
process.stderr.write(r.stderr);
|
|
346
|
+
process.stderr.write(`[onboard] sponsio scan failed (${r.status}): ${errMsg}\n` +
|
|
347
|
+
" Falling back to a minimal `sponsio.yaml` (det-only NL rules).\n");
|
|
348
|
+
method = "fallback";
|
|
349
|
+
message = "sponsio scan failed — used minimal det-only yaml fallback instead.";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (method === "fallback") {
|
|
353
|
+
const payload = buildFallbackPayload(toolNames, invJson, opts.agent, opts.mode);
|
|
354
|
+
const header = "# Generated by: sponsio onboard (Python CLI missing or `sponsio scan` errored)\n" +
|
|
355
|
+
"# Re-run with `pip install sponsio` on PATH for full `sponsio scan` / pack inference.\n";
|
|
356
|
+
(0, fs_1.writeFileSync)(outPath, header + (0, js_yaml_1.dump)(payload, { lineWidth: 100, noRefs: true }), "utf-8");
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
(0, fs_1.unlinkSync)(absTmp);
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
// best-effort temp cleanup
|
|
363
|
+
}
|
|
364
|
+
const fw = detectFramework(root);
|
|
365
|
+
const wrapSnippet = wrapSnippetFor(fw, opts.agent, configRelForSnippet);
|
|
366
|
+
if (method === "fallback" && message) {
|
|
367
|
+
process.stderr.write(`[onboard] ${message}\n`);
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
outPath,
|
|
371
|
+
root,
|
|
372
|
+
agent: opts.agent,
|
|
373
|
+
method,
|
|
374
|
+
toolCount: toolNames.length,
|
|
375
|
+
message,
|
|
376
|
+
wrapSnippet,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/* ----------------------------------------------------------------- */
|
|
380
|
+
function parseOnboardArgs(argv) {
|
|
381
|
+
const out = {
|
|
382
|
+
target: ".",
|
|
383
|
+
agent: "agent",
|
|
384
|
+
mode: "observe",
|
|
385
|
+
force: false,
|
|
386
|
+
llm: false,
|
|
387
|
+
pyNever: false,
|
|
388
|
+
help: false,
|
|
389
|
+
emitContext: false,
|
|
390
|
+
};
|
|
391
|
+
for (let i = 0; i < argv.length; i++) {
|
|
392
|
+
const a = argv[i];
|
|
393
|
+
if (a === "-h" || a === "--help") {
|
|
394
|
+
out.help = true;
|
|
395
|
+
}
|
|
396
|
+
else if (a === "--force") {
|
|
397
|
+
out.force = true;
|
|
398
|
+
}
|
|
399
|
+
else if (a === "--llm") {
|
|
400
|
+
out.llm = true;
|
|
401
|
+
}
|
|
402
|
+
else if (a === "--py-never" || a === "--no-python") {
|
|
403
|
+
out.pyNever = true;
|
|
404
|
+
}
|
|
405
|
+
else if (a === "--agent" || a === "-a") {
|
|
406
|
+
out.agent = argv[++i] ?? "agent";
|
|
407
|
+
}
|
|
408
|
+
else if (a === "--mode") {
|
|
409
|
+
// Parity with the Python CLI's ``sponsio onboard --mode``.
|
|
410
|
+
// ``sponsio init`` dispatches with ``--mode <observe|enforce>``
|
|
411
|
+
// for both Python and TS projects; without this branch the
|
|
412
|
+
// TS scanner errored with ``unknown flag: --mode`` and the
|
|
413
|
+
// wizard's apply step bailed mid-flight.
|
|
414
|
+
const v = argv[++i];
|
|
415
|
+
if (v !== "observe" && v !== "enforce") {
|
|
416
|
+
process.stderr.write(`--mode must be 'observe' or 'enforce' (got: ${v ?? "<none>"})\n`);
|
|
417
|
+
process.exit(2);
|
|
418
|
+
}
|
|
419
|
+
out.mode = v;
|
|
420
|
+
}
|
|
421
|
+
else if (a === "--push") {
|
|
422
|
+
out.push = true;
|
|
423
|
+
}
|
|
424
|
+
else if (a === "--no-push") {
|
|
425
|
+
out.push = false;
|
|
426
|
+
}
|
|
427
|
+
else if (a === "--push-url") {
|
|
428
|
+
out.pushUrl = argv[++i];
|
|
429
|
+
}
|
|
430
|
+
else if (a === "--emit-context") {
|
|
431
|
+
out.emitContext = true;
|
|
432
|
+
}
|
|
433
|
+
else if (a.startsWith("-")) {
|
|
434
|
+
process.stderr.write(`unknown flag: ${a}\n`);
|
|
435
|
+
process.exit(2);
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
out.target = a;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
const ONBOARD_HELP = [
|
|
444
|
+
"sponsio onboard — static-scan JS/TS, write sponsio.yaml, print wrap snippet",
|
|
445
|
+
"",
|
|
446
|
+
"USAGE:",
|
|
447
|
+
" sponsio onboard [path] [options] # path = project root (default: .)",
|
|
448
|
+
"",
|
|
449
|
+
" When the ``sponsio`` CLI (Python) is on PATH, runs the same",
|
|
450
|
+
" ``sponsio scan <tool-inventory.json>`` pass as a manual",
|
|
451
|
+
" ``npx @sponsio/sdk | sponsio`` pipeline — so you get the",
|
|
452
|
+
" same starter contracts / packs as ``sponsio onboard .`` in a",
|
|
453
|
+
" Python repo. Without ``sponsio``, writes a det-only minimal yaml",
|
|
454
|
+
" (observe mode) that ``new Sponsio({ config: 'sponsio.yaml' })``",
|
|
455
|
+
" can still load in TypeScript.",
|
|
456
|
+
"",
|
|
457
|
+
"OPTIONS:",
|
|
458
|
+
" -a, --agent <id> Agent id in yaml (default: agent, matches `sponsio scan`)",
|
|
459
|
+
" --llm Pass --llm to `sponsio scan` (needs API keys; see docs/cli.md#provider-matrix)",
|
|
460
|
+
" --push Push the generated yaml to the local dashboard (needs `sponsio serve --dev` and Python)",
|
|
461
|
+
" --push-url <url> Dashboard URL (default: http://127.0.0.1:8000)",
|
|
462
|
+
" --force Overwrite an existing sponsio.yaml",
|
|
463
|
+
" --py-never Never call Python; always write the det-only fallback",
|
|
464
|
+
" --emit-context Skip writing the yaml; emit structured project context",
|
|
465
|
+
" (framework / tool inventory / packs / existing yaml /",
|
|
466
|
+
" policy docs / wrap snippet) as JSON to stdout. Pair",
|
|
467
|
+
" with `sponsio prompt onboard` to drive the IDE agent",
|
|
468
|
+
" through contract authoring without a separate LLM call.",
|
|
469
|
+
" -h, --help Show this help",
|
|
470
|
+
"",
|
|
471
|
+
"REQUIRES: npm `yaml` for `@sponsio/sdk` when using `config:` —",
|
|
472
|
+
" `npm install yaml` (pulled in automatically if you add `@sponsio/sdk` and follow its install line).",
|
|
473
|
+
"",
|
|
474
|
+
].join("\n");
|
|
475
|
+
/**
|
|
476
|
+
* Find files that look like agent entry points — the ones that import
|
|
477
|
+
* the model/framework and call it. The IDE agent uses this to avoid
|
|
478
|
+
* a second discovery pass. Conservative scan: top-level + a few
|
|
479
|
+
* common dirs only.
|
|
480
|
+
*/
|
|
481
|
+
async function detectEntryFileCandidates(root, framework) {
|
|
482
|
+
// Provider/framework signals to grep for, by detected framework.
|
|
483
|
+
const signals = {
|
|
484
|
+
vercel: [
|
|
485
|
+
/\bgenerateText\s*\(/,
|
|
486
|
+
/from\s+["']ai["']/,
|
|
487
|
+
/from\s+["']@ai-sdk\//,
|
|
488
|
+
],
|
|
489
|
+
claude: [/from\s+["']@anthropic-ai\/claude-agent-sdk["']/, /from\s+["']claude-agent-sdk["']/],
|
|
490
|
+
langgraph: [/from\s+["']@langchain\/langgraph["']/, /createReactAgent\s*\(/],
|
|
491
|
+
mcp: [/from\s+["']@modelcontextprotocol\/sdk\//],
|
|
492
|
+
google_adk: [/from\s+["']@google\/adk["']/],
|
|
493
|
+
openai: [/from\s+["']openai["']/, /\bnew\s+OpenAI\s*\(/],
|
|
494
|
+
none: [],
|
|
495
|
+
};
|
|
496
|
+
const fwSignals = signals[framework] ?? [];
|
|
497
|
+
if (fwSignals.length === 0)
|
|
498
|
+
return [];
|
|
499
|
+
const patterns = ["*.ts", "*.tsx", "*.js", "*.jsx", "src/**/*.{ts,tsx,js,jsx}", "app/**/*.{ts,tsx,js,jsx}"];
|
|
500
|
+
const files = await (0, fast_glob_1.default)(patterns, {
|
|
501
|
+
cwd: root,
|
|
502
|
+
onlyFiles: true,
|
|
503
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/__tests__/**", "**/*.test.*", "**/*.spec.*"],
|
|
504
|
+
});
|
|
505
|
+
const out = [];
|
|
506
|
+
for (const rel of files) {
|
|
507
|
+
let text;
|
|
508
|
+
try {
|
|
509
|
+
text = (0, fs_1.readFileSync)((0, path_1.join)(root, rel), "utf-8");
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
const matched = [];
|
|
515
|
+
for (const re of fwSignals) {
|
|
516
|
+
if (re.test(text))
|
|
517
|
+
matched.push(re.source);
|
|
518
|
+
}
|
|
519
|
+
if (matched.length > 0)
|
|
520
|
+
out.push({ path: rel, reason: `matches: ${matched.join(", ")}` });
|
|
521
|
+
}
|
|
522
|
+
// Prefer fewer, deeper signals first (more matches usually = real entry).
|
|
523
|
+
out.sort((a, b) => b.reason.length - a.reason.length);
|
|
524
|
+
return out.slice(0, 5);
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Discover root-level policy docs (security.md / policy.md and case
|
|
528
|
+
* variants) and bundle them with the onboard context payload. Mirrors
|
|
529
|
+
* Python's ``policy_docs`` field.
|
|
530
|
+
*/
|
|
531
|
+
function collectPolicyDocs(root) {
|
|
532
|
+
const docs = [];
|
|
533
|
+
const candidates = ["security.md", "SECURITY.md", "policy.md", "POLICY.md"];
|
|
534
|
+
const seen = new Set();
|
|
535
|
+
for (const name of candidates) {
|
|
536
|
+
const p = (0, path_1.join)(root, name);
|
|
537
|
+
if (!(0, fs_1.existsSync)(p))
|
|
538
|
+
continue;
|
|
539
|
+
try {
|
|
540
|
+
const real = (0, fs_1.realpathSync)(p);
|
|
541
|
+
if (seen.has(real))
|
|
542
|
+
continue;
|
|
543
|
+
seen.add(real);
|
|
544
|
+
docs.push({ path: name, content: (0, fs_1.readFileSync)(p, "utf-8") });
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
// skip unreadable
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return docs;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Run the deterministic stages of onboard (framework detection + AST
|
|
554
|
+
* scan + pack auto-select + policy doc discovery) and emit the
|
|
555
|
+
* structured payload as JSON. Mirrors Python's ``--emit-context``.
|
|
556
|
+
*/
|
|
557
|
+
async function runEmitContext(opts) {
|
|
558
|
+
const { root, outPath, configRelForSnippet } = resolveOnboardPaths(opts.target);
|
|
559
|
+
if (!(0, fs_1.existsSync)(root)) {
|
|
560
|
+
process.stderr.write(`[onboard] not a path: ${root}\n`);
|
|
561
|
+
process.exit(1);
|
|
562
|
+
}
|
|
563
|
+
const framework = detectFramework(root);
|
|
564
|
+
const patterns = defaultScanGlobs(root);
|
|
565
|
+
const so = await (0, index_1.scan)(patterns, { cwd: root });
|
|
566
|
+
const tools = so.tools.map((t) => t.function);
|
|
567
|
+
const wrapSnippet = wrapSnippetFor(framework, opts.agent, configRelForSnippet);
|
|
568
|
+
let existingYaml = "";
|
|
569
|
+
if ((0, fs_1.existsSync)(outPath)) {
|
|
570
|
+
try {
|
|
571
|
+
existingYaml = (0, fs_1.readFileSync)(outPath, "utf-8");
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
// ignore
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const entryCandidates = await detectEntryFileCandidates(root, framework);
|
|
578
|
+
// Mirror Python's ``health`` + ``pre_existing_contracts`` so the
|
|
579
|
+
// ``sponsio prompt onboard`` template's stop-and-ask gate and
|
|
580
|
+
// dedupe rule work identically across Python and TS callers.
|
|
581
|
+
// Cross-language parity: the IDE-agent wizard prompt branches on
|
|
582
|
+
// ``runtime`` to pick which emit-context binary to call, but the
|
|
583
|
+
// JSON shape both spit out has to be the same or the template's
|
|
584
|
+
// ``health == "empty" → stop`` branch silently never triggers
|
|
585
|
+
// for TS projects.
|
|
586
|
+
const policyDocs = collectPolicyDocs(root);
|
|
587
|
+
let health;
|
|
588
|
+
let healthDetail;
|
|
589
|
+
if (framework !== "none" && tools.length > 0) {
|
|
590
|
+
health = "ok";
|
|
591
|
+
healthDetail = "framework + tools detected";
|
|
592
|
+
}
|
|
593
|
+
else if (tools.length > 0) {
|
|
594
|
+
health = "tools_only";
|
|
595
|
+
healthDetail =
|
|
596
|
+
"tool_inventory found tools but no framework dep matched in " +
|
|
597
|
+
"package.json — pick framework manually if the panel default " +
|
|
598
|
+
"is wrong";
|
|
599
|
+
}
|
|
600
|
+
else if (framework !== "none") {
|
|
601
|
+
health = "tools_only";
|
|
602
|
+
healthDetail =
|
|
603
|
+
`framework ${framework} detected but tool_inventory is empty — ` +
|
|
604
|
+
"agent likely uses external SDK tools (MCP, prebuilt LangChain " +
|
|
605
|
+
"tools, OpenAI JSON schemas); grep the repo for tool registration";
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
health = "empty";
|
|
609
|
+
healthDetail =
|
|
610
|
+
"no framework, no tools — wrong scan path (monorepo + agent " +
|
|
611
|
+
"in subdir), or this is a bare function-calling loop, or the " +
|
|
612
|
+
"project is Python and you ran the TS probe";
|
|
613
|
+
}
|
|
614
|
+
// Best-effort parse of the on-disk yaml's ``contracts:`` block so
|
|
615
|
+
// the template's "skip any (pattern, primary_arg) already in
|
|
616
|
+
// pre_existing_contracts" rule has data to dedupe against. Light
|
|
617
|
+
// parser — strict YAML lib failures shouldn't block the diagnostic
|
|
618
|
+
// JSON, just leave the list empty.
|
|
619
|
+
const preExistingContracts = [];
|
|
620
|
+
if (existingYaml) {
|
|
621
|
+
try {
|
|
622
|
+
const yamlMod = await Promise.resolve().then(() => __importStar(require("js-yaml")));
|
|
623
|
+
const data = yamlMod.load(existingYaml);
|
|
624
|
+
if (data && typeof data === "object" && "agents" in data) {
|
|
625
|
+
const agents = data.agents;
|
|
626
|
+
const agentBlock = agents?.[opts.agent];
|
|
627
|
+
if (agentBlock && typeof agentBlock === "object") {
|
|
628
|
+
const contracts = agentBlock.contracts;
|
|
629
|
+
if (Array.isArray(contracts)) {
|
|
630
|
+
for (const c of contracts) {
|
|
631
|
+
if (!c || typeof c !== "object")
|
|
632
|
+
continue;
|
|
633
|
+
const body = c.E && typeof c.E === "object"
|
|
634
|
+
? c.E
|
|
635
|
+
: c;
|
|
636
|
+
const pattern = body.pattern;
|
|
637
|
+
if (typeof pattern !== "string")
|
|
638
|
+
continue;
|
|
639
|
+
preExistingContracts.push({
|
|
640
|
+
pattern,
|
|
641
|
+
args: Array.isArray(body.args) ? body.args : [],
|
|
642
|
+
source: typeof body.source === "string"
|
|
643
|
+
? body.source
|
|
644
|
+
: typeof c.source === "string"
|
|
645
|
+
? (c.source)
|
|
646
|
+
: "",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
// Light parser; on any error leave preExistingContracts empty.
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const payload = {
|
|
658
|
+
health,
|
|
659
|
+
health_detail: healthDetail,
|
|
660
|
+
framework: { name: framework, evidence: "from package.json dependencies" },
|
|
661
|
+
agent_id: opts.agent,
|
|
662
|
+
tool_inventory: tools,
|
|
663
|
+
auto_selected_packs: ["sponsio:core/universal"],
|
|
664
|
+
needs_workspace: false,
|
|
665
|
+
existing_yaml: existingYaml,
|
|
666
|
+
pre_existing_contracts: preExistingContracts,
|
|
667
|
+
policy_docs: policyDocs,
|
|
668
|
+
wrap_snippet: wrapSnippet,
|
|
669
|
+
entry_file_candidates: entryCandidates,
|
|
670
|
+
out_path: outPath,
|
|
671
|
+
next_steps_hint: "Run `npx sponsio prompt onboard` to get the contract-authoring " +
|
|
672
|
+
"prompt template, apply it to this JSON in your own LLM context, " +
|
|
673
|
+
`then write the resulting YAML to ${outPath} via Edit/Write, ` +
|
|
674
|
+
"and patch the agent entry file (see `entry_file_candidates`) " +
|
|
675
|
+
"with the wrap_snippet. Validate with `npx sponsio validate`.",
|
|
676
|
+
};
|
|
677
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* ``main`` for ``sponsio onboard ...`` — *sync entry from cli.ts*.
|
|
681
|
+
*/
|
|
682
|
+
async function runOnboardCli(argv) {
|
|
683
|
+
const p = parseOnboardArgs(argv);
|
|
684
|
+
if (p.help) {
|
|
685
|
+
process.stdout.write(ONBOARD_HELP);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (p.emitContext) {
|
|
689
|
+
await runEmitContext({
|
|
690
|
+
target: p.target,
|
|
691
|
+
agent: p.agent,
|
|
692
|
+
mode: p.mode,
|
|
693
|
+
force: p.force,
|
|
694
|
+
llm: p.llm,
|
|
695
|
+
pyNever: p.pyNever,
|
|
696
|
+
});
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const res = await runOnboard({
|
|
700
|
+
target: p.target,
|
|
701
|
+
agent: p.agent,
|
|
702
|
+
mode: p.mode,
|
|
703
|
+
force: p.force,
|
|
704
|
+
push: p.push,
|
|
705
|
+
pushUrl: p.pushUrl,
|
|
706
|
+
llm: p.llm,
|
|
707
|
+
pyNever: p.pyNever,
|
|
708
|
+
});
|
|
709
|
+
process.stdout.write("\n");
|
|
710
|
+
// Match Python ``sponsio onboard``'s recap shape so the wizard
|
|
711
|
+
// → onboard transition reads as one program. The bullet-list
|
|
712
|
+
// ``· framework: …`` block was redundant with the ``✓ wrote
|
|
713
|
+
// <path>`` summary that ``sponsio scan`` already printed when
|
|
714
|
+
// method === python; drop it and use Python's "Add this to
|
|
715
|
+
// your agent entry point:" / "Next:" framing for the rest.
|
|
716
|
+
if (res.method === "fallback") {
|
|
717
|
+
// Fallback path didn't go through ``sponsio scan``, so it
|
|
718
|
+
// didn't print the summary itself — emit one here in the
|
|
719
|
+
// same shape Python uses (``✓ <path>`` + col-6 sub-keys
|
|
720
|
+
// under the col-2 ✓ marker). Colours mirror Python's
|
|
721
|
+
// ``click.secho(f" ✓ {report.out_path}", fg="green")``.
|
|
722
|
+
process.stdout.write(` ${tokens_1.style.success("✓")} ${res.outPath}\n`);
|
|
723
|
+
process.stdout.write(` tools: ${res.toolCount}\n`);
|
|
724
|
+
process.stdout.write(` mode: ${p.mode}\n`);
|
|
725
|
+
process.stdout.write(" method: det-only fallback yaml\n");
|
|
726
|
+
process.stdout.write("\n");
|
|
727
|
+
}
|
|
728
|
+
process.stdout.write(` ${tokens_1.style.brand("Add this to your agent entry point:", true)}\n\n`);
|
|
729
|
+
for (const line of res.wrapSnippet.split("\n")) {
|
|
730
|
+
process.stdout.write(` ${tokens_1.style.fg(line)}\n`);
|
|
731
|
+
}
|
|
732
|
+
// When dispatched from ``sponsio init`` the wizard prints its own
|
|
733
|
+
// picks-aware ``Next:`` block; emitting ours here too produces a
|
|
734
|
+
// duplicate. ``SPONSIO_INIT_DISPATCH=1`` is the same suppress
|
|
735
|
+
// signal Python's ``cli.py`` already honours for its onboard
|
|
736
|
+
// recap (cli.py:4169, cli.py:4800), so the convention transfers.
|
|
737
|
+
if (process.env.SPONSIO_INIT_DISPATCH) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
process.stdout.write(`\n ${tokens_1.style.brand("Next:", true)}\n`);
|
|
741
|
+
// Each next-step line: command in brand cyan, ``# comment`` in
|
|
742
|
+
// metadata grey — same split Python's ``cta_line`` / ``Next:``
|
|
743
|
+
// sections use.
|
|
744
|
+
const nextSteps = [
|
|
745
|
+
["npx sponsio doctor", "# env health"],
|
|
746
|
+
["npx sponsio validate", "# parse check"],
|
|
747
|
+
["npx sponsio report --since 24h", "# what would have been blocked"],
|
|
748
|
+
["npx sponsio mode enforce", "# one-shot flip when ready"],
|
|
749
|
+
];
|
|
750
|
+
for (const [cmd, comment] of nextSteps) {
|
|
751
|
+
// Pad commands to a fixed width so comments line up vertically —
|
|
752
|
+
// same approach as Python's hand-tuned column widths in cli.py's
|
|
753
|
+
// onboard recap.
|
|
754
|
+
const pad = " ".repeat(Math.max(1, 38 - cmd.length));
|
|
755
|
+
process.stdout.write(` ${tokens_1.style.brand(cmd)}${pad}${tokens_1.style.metadata(comment)}\n`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
//# sourceMappingURL=onboard.js.map
|