@amityco/social-plus-vise 0.4.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/dist/server.js ADDED
@@ -0,0 +1,810 @@
1
+ #!/usr/bin/env node
2
+ import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
+ import { attestRule, attestRuleTool, checkCompliance, checkComplianceTool, explainRule, explainRuleTool, initCompliance, initComplianceTool, initEngagement, initEngagementTool, showEngagement, showEngagementTool, statusCompliance, syncCompliance, syncComplianceTool, } from "./tools/compliance.js";
10
+ import { getDocPageTool, searchDocsTool } from "./tools/docs.js";
11
+ import { planHarnessTool } from "./tools/harness.js";
12
+ import { planIntegrationTool } from "./tools/integration.js";
13
+ import { inspectProjectTool, validateSetupTool } from "./tools/project.js";
14
+ import { resolveRequestTool, suggestPatchTool } from "./tools/resolve.js";
15
+ import { runSensorsTool } from "./tools/sensors.js";
16
+ import { packageName, packageVersion } from "./version.js";
17
+ const tools = new Map([
18
+ searchDocsTool,
19
+ getDocPageTool,
20
+ inspectProjectTool,
21
+ planHarnessTool,
22
+ planIntegrationTool,
23
+ initComplianceTool,
24
+ checkComplianceTool,
25
+ syncComplianceTool,
26
+ attestRuleTool,
27
+ explainRuleTool,
28
+ initEngagementTool,
29
+ showEngagementTool,
30
+ resolveRequestTool,
31
+ runSensorsTool,
32
+ validateSetupTool,
33
+ suggestPatchTool,
34
+ ].map((tool) => [tool.name, tool]));
35
+ const bundledSkillName = "social-plus-vise";
36
+ const cliResult = await handleCli(process.argv.slice(2));
37
+ if (cliResult === "exit") {
38
+ process.exitCode = process.exitCode ?? 0;
39
+ }
40
+ else {
41
+ const server = new Server({
42
+ name: "social-plus-vise",
43
+ version: packageVersion,
44
+ }, {
45
+ capabilities: {
46
+ tools: {},
47
+ },
48
+ });
49
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
50
+ tools: Array.from(tools.values()).map((tool) => ({
51
+ name: tool.name,
52
+ description: tool.description,
53
+ inputSchema: tool.inputSchema,
54
+ })),
55
+ }));
56
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
57
+ const tool = tools.get(request.params.name);
58
+ if (!tool) {
59
+ throw new Error(`Unknown tool: ${request.params.name}`);
60
+ }
61
+ return tool.call(request.params.arguments);
62
+ });
63
+ await server.connect(new StdioServerTransport());
64
+ }
65
+ async function handleCli(args) {
66
+ const command = args[0];
67
+ if (!command) {
68
+ return "serve";
69
+ }
70
+ if (command === "mcp" || command === "serve" || command === "start") {
71
+ return "serve";
72
+ }
73
+ if (command === "--version" || command === "-v" || command === "version") {
74
+ console.log(`${packageName} ${packageVersion}`);
75
+ return "exit";
76
+ }
77
+ if (command === "--help" || command === "-h" || command === "help") {
78
+ console.log(helpText(args[1]));
79
+ return "exit";
80
+ }
81
+ if (command === "doctor" || command === "--doctor") {
82
+ console.log(JSON.stringify(doctorResult(), null, 2));
83
+ return "exit";
84
+ }
85
+ if (isHelpRequest(args)) {
86
+ console.log(helpText(command));
87
+ return "exit";
88
+ }
89
+ try {
90
+ if (command === "install-skill" || command === "install_skill") {
91
+ console.log(JSON.stringify(await installSkill(args.slice(1)), null, 2));
92
+ return "exit";
93
+ }
94
+ if (command === "print-skill" || command === "print_skill") {
95
+ console.log(await readFile(path.join(skillSourceDir(), "SKILL.md"), "utf8"));
96
+ return "exit";
97
+ }
98
+ if (command === "skill-path" || command === "skill_path") {
99
+ console.log(JSON.stringify(skillPathResult(), null, 2));
100
+ return "exit";
101
+ }
102
+ if (command === "search-docs" || command === "search_docs") {
103
+ await printToolResult(searchDocsTool, {
104
+ query: flagValue(args, "query") ?? requiredPositionalText(args.slice(1), "search-docs requires a query."),
105
+ limit: optionalNumberFlag(args, "limit"),
106
+ });
107
+ return "exit";
108
+ }
109
+ if (command === "get-doc-page" || command === "get_doc_page") {
110
+ await printToolResult(getDocPageTool, {
111
+ path: flagValue(args, "path") ?? requiredPositionalText(args.slice(1), "get-doc-page requires a docs path."),
112
+ });
113
+ return "exit";
114
+ }
115
+ if (command === "inspect") {
116
+ await printToolResult(inspectProjectTool, {
117
+ repoPath: positionalRepoPath(args.slice(1)),
118
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
119
+ });
120
+ return "exit";
121
+ }
122
+ if (command === "plan" || command === "plan-integration") {
123
+ await printToolResult(planIntegrationTool, {
124
+ repoPath: positionalRepoPath(args.slice(1)),
125
+ request: requiredFlagValue(args, "request", "plan requires --request."),
126
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
127
+ answers: keyValueFlag(args, "answer"),
128
+ });
129
+ return "exit";
130
+ }
131
+ if (command === "plan-harness") {
132
+ await printToolResult(planHarnessTool, {
133
+ repoPath: positionalRepoPath(args.slice(1)),
134
+ request: requiredFlagValue(args, "request", "plan-harness requires --request."),
135
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
136
+ });
137
+ return "exit";
138
+ }
139
+ if (command === "validate" || command === "validate-setup") {
140
+ await printToolResult(validateSetupTool, {
141
+ repoPath: positionalRepoPath(args.slice(1)),
142
+ platform: flagValue(args, "platform"),
143
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
144
+ });
145
+ return "exit";
146
+ }
147
+ if (command === "run-sensors" || command === "run_sensor" || command === "run-sensor") {
148
+ await printToolResult(runSensorsTool, {
149
+ repoPath: positionalRepoPath(args.slice(1)),
150
+ request: flagValue(args, "request"),
151
+ include: flagValues(args, "include"),
152
+ timeoutMs: optionalNumberFlag(args, "timeout-ms"),
153
+ dryRun: hasFlag(args, "dry-run"),
154
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
155
+ });
156
+ return "exit";
157
+ }
158
+ if (command === "resolve") {
159
+ await printToolResult(resolveRequestTool, {
160
+ repoPath: positionalRepoPath(args.slice(1)),
161
+ request: requiredFlagValue(args, "request", "resolve requires --request."),
162
+ surfacePath: flagValue(args, "surface") ?? flagValue(args, "surface-path"),
163
+ });
164
+ return "exit";
165
+ }
166
+ if (command === "init") {
167
+ assertOnlyKnownFlags(args, ["request", "surface", "surface-path"], "init");
168
+ console.log(JSON.stringify(await initCompliance(positionalRepoPath(args.slice(1)), requiredFlagValue(args, "request", "init requires --request."), flagValue(args, "surface") ?? flagValue(args, "surface-path")), null, 2));
169
+ return "exit";
170
+ }
171
+ if (command === "check") {
172
+ assertOnlyKnownFlags(args, ["ci"], "check");
173
+ const result = await checkCompliance(positionalRepoPath(args.slice(1)));
174
+ console.log(JSON.stringify(hasFlag(args, "ci") ? ciCheckResult(result) : result, null, 2));
175
+ process.exitCode = result.exitCode;
176
+ return "exit";
177
+ }
178
+ if (command === "sync") {
179
+ assertOnlyKnownFlags(args, [], "sync");
180
+ console.log(JSON.stringify(await syncCompliance(positionalRepoPath(args.slice(1))), null, 2));
181
+ return "exit";
182
+ }
183
+ if (command === "attest") {
184
+ assertOnlyKnownFlags(args, ["rule", "confidence", "signer", "identity", "rationale", "evidence-file"], "attest");
185
+ console.log(JSON.stringify(await attestRule({
186
+ repoPath: positionalRepoPath(args.slice(1)),
187
+ ruleId: requiredFlagValue(args, "rule", "attest requires --rule."),
188
+ confidence: requiredConfidence(args),
189
+ signer: requiredSigner(args),
190
+ identity: flagValue(args, "identity"),
191
+ rationale: requiredFlagValue(args, "rationale", "attest requires --rationale."),
192
+ evidence: await evidenceFromArgs(args),
193
+ }), null, 2));
194
+ return "exit";
195
+ }
196
+ if (command === "explain") {
197
+ assertOnlyKnownFlags(args, [], "explain");
198
+ console.log(JSON.stringify(await explainRule(requiredPositionalText(args.slice(1), "explain requires a rule id.")), null, 2));
199
+ return "exit";
200
+ }
201
+ if (command === "status") {
202
+ assertOnlyKnownFlags(args, [], "status");
203
+ console.log(JSON.stringify(await statusCompliance(positionalRepoPath(args.slice(1))), null, 2));
204
+ return "exit";
205
+ }
206
+ if (command === "engagement") {
207
+ const sub = args[1];
208
+ const subArgs = args.slice(2);
209
+ if (sub === "init") {
210
+ assertOnlyKnownFlags(subArgs, ["tier", "customer-id", "scope", "target-completion", "reviewer-name", "reviewer-email", "evidence-upload-consent"], "engagement init");
211
+ console.log(JSON.stringify(await initEngagement({
212
+ repoPath: positionalRepoPath(subArgs),
213
+ tier: flagValue(subArgs, "tier"),
214
+ customerId: flagValue(subArgs, "customer-id"),
215
+ scope: flagValues(subArgs, "scope").flatMap((value) => value.split(",").map((entry) => entry.trim()).filter(Boolean)),
216
+ targetCompletionDate: flagValue(subArgs, "target-completion"),
217
+ reviewerName: flagValue(subArgs, "reviewer-name"),
218
+ reviewerEmail: flagValue(subArgs, "reviewer-email"),
219
+ evidenceUploadConsent: hasFlag(subArgs, "evidence-upload-consent"),
220
+ }), null, 2));
221
+ return "exit";
222
+ }
223
+ if (sub === "show") {
224
+ assertOnlyKnownFlags(subArgs, [], "engagement show");
225
+ console.log(JSON.stringify(await showEngagement(positionalRepoPath(subArgs)), null, 2));
226
+ return "exit";
227
+ }
228
+ console.error(`Unknown engagement subcommand: ${sub ?? "(none)"}. Expected "init" or "show".`);
229
+ process.exitCode = 1;
230
+ return "exit";
231
+ }
232
+ }
233
+ catch (error) {
234
+ console.error(error instanceof Error ? error.message : String(error));
235
+ process.exitCode = 1;
236
+ return "exit";
237
+ }
238
+ if (command.startsWith("-")) {
239
+ console.error(`Unknown option: ${command}`);
240
+ console.error("Run with --help for usage.");
241
+ process.exitCode = 1;
242
+ return "exit";
243
+ }
244
+ console.error(`Unknown command: ${command}`);
245
+ console.error("Run with --help for usage.");
246
+ process.exitCode = 1;
247
+ return "exit";
248
+ }
249
+ function helpText(command) {
250
+ if (command === "inspect") {
251
+ return `${packageName} inspect
252
+
253
+ Inspect a customer repository and detect platform, app surface, and design-system signals.
254
+
255
+ Usage:
256
+ vise inspect [repoPath] [--surface apps/web]
257
+ vise inspect [repoPath] [--surface apps/web]`;
258
+ }
259
+ if (command === "plan" || command === "plan-integration") {
260
+ return `${packageName} plan
261
+
262
+ Create the evidence-backed implementation packet before editing code.
263
+
264
+ Usage:
265
+ vise plan [repoPath] --request "Add a social feed"
266
+ vise plan apps/web --request "Create posts" --surface apps/web
267
+
268
+ Re-plan with collected answers (repeat --answer for each intake question):
269
+ vise plan . --request "Add a social feed" \\
270
+ --answer feed_scope=community \\
271
+ --answer feed_target=existing\\ communityId \\
272
+ --answer target_screen_or_route=app/feed/page.tsx`;
273
+ }
274
+ if (command === "plan-harness") {
275
+ return `${packageName} plan-harness
276
+
277
+ Create a harness guide/sensor plan for the requested SDK integration.
278
+
279
+ Usage:
280
+ vise plan-harness [repoPath] --request "Add a social feed"
281
+ vise plan-harness apps/web --request "Set up notifications" --surface apps/web`;
282
+ }
283
+ if (command === "search-docs" || command === "search_docs") {
284
+ return `${packageName} search-docs
285
+
286
+ Search social.plus documentation from the hosted llms source, or SOCIAL_PLUS_DOCS_ROOT for maintainers.
287
+
288
+ Usage:
289
+ vise search-docs "create text post"
290
+ vise search-docs --query "Live Collection cleanup" --limit 3`;
291
+ }
292
+ if (command === "install-skill" || command === "install_skill") {
293
+ return `${packageName} install-skill
294
+
295
+ Install the bundled social.plus Vise skill for a host AI coding tool.
296
+
297
+ Usage:
298
+ vise install-skill --target codex
299
+ vise install-skill --target claude
300
+ vise install-skill --target claude-project .
301
+ vise install-skill --target agents .
302
+ vise install-skill --target cursor .
303
+ vise install-skill --target cursor-rules .
304
+ vise install-skill --target vscode .
305
+ vise install-skill --target copilot .
306
+ vise install-skill --dest ~/.codex/skills
307
+ vise install-skill --dest ~/.codex/skills --force
308
+
309
+ Targets:
310
+ codex Installs to ~/.codex/skills/social-plus-vise
311
+ claude Installs to ~/.claude/skills/social-plus-vise
312
+ claude-project Installs to <repoPath>/.claude/skills/social-plus-vise
313
+ agents Installs to <repoPath>/.agents/skills/social-plus-vise
314
+ cursor Installs to <repoPath>/.cursor/skills/social-plus-vise
315
+ vscode Installs to <repoPath>/.github/skills/social-plus-vise
316
+ copilot Installs to <repoPath>/.github/skills/social-plus-vise
317
+ cursor-rules Writes <repoPath>/.cursor/rules/social-plus-vise.mdc
318
+ custom Use --dest <skillsRoot or exact skill directory>`;
319
+ }
320
+ if (command === "print-skill" || command === "print_skill") {
321
+ return `${packageName} print-skill
322
+
323
+ Print the bundled social.plus Vise skill markdown to stdout.
324
+
325
+ Usage:
326
+ vise print-skill`;
327
+ }
328
+ if (command === "skill-path" || command === "skill_path") {
329
+ return `${packageName} skill-path
330
+
331
+ Print the bundled skill source path and supported install targets.
332
+
333
+ Usage:
334
+ vise skill-path`;
335
+ }
336
+ if (command === "get-doc-page" || command === "get_doc_page") {
337
+ return `${packageName} get-doc-page
338
+
339
+ Read one social.plus documentation page by canonical docs path.
340
+
341
+ Usage:
342
+ vise get-doc-page social-plus-sdk/social/content-management/posts/creation/text-post
343
+ vise get-doc-page --path uikit/getting-started/installation`;
344
+ }
345
+ if (command === "validate" || command === "validate-setup") {
346
+ return `${packageName} validate
347
+
348
+ Run deterministic social.plus setup validation for the current project.
349
+
350
+ Usage:
351
+ vise validate [repoPath] [--platform typescript] [--surface apps/web]`;
352
+ }
353
+ if (command === "run-sensors" || command === "run-sensor" || command === "run_sensor") {
354
+ return `${packageName} run-sensors
355
+
356
+ Run detected project command sensors such as typecheck, test, build, Flutter, or Gradle checks.
357
+
358
+ Usage:
359
+ vise run-sensors [repoPath] [--dry-run] [--include "npm test"] [--timeout-ms 120000]`;
360
+ }
361
+ if (command === "resolve") {
362
+ return `${packageName} resolve
363
+
364
+ Resolve a natural-language request into the closest supported Vise outcome.
365
+
366
+ Usage:
367
+ vise resolve [repoPath] --request "Add a social feed"`;
368
+ }
369
+ if (command === "init") {
370
+ return `${packageName} init
371
+
372
+ Initialize the local sp-vise compliance sidecar for one integration request.
373
+
374
+ Usage:
375
+ vise init [repoPath] --request "Add a social feed" [--surface apps/web]`;
376
+ }
377
+ if (command === "check") {
378
+ return `${packageName} check
379
+
380
+ Check the current source and recorded attestations against the compliance contract. Read-only.
381
+
382
+ Usage:
383
+ vise check [repoPath] [--ci]`;
384
+ }
385
+ if (command === "sync") {
386
+ return `${packageName} sync
387
+
388
+ Persist deterministic-pass compliance evidence into sp-vise/attestations.
389
+
390
+ Usage:
391
+ vise sync [repoPath]`;
392
+ }
393
+ if (command === "attest") {
394
+ return `${packageName} attest
395
+
396
+ Record a host-agent or local-human attestation for one compliance rule.
397
+
398
+ Usage:
399
+ vise attest [repoPath] --rule sdk.init.at-startup --confidence high --signer host-agent --evidence-file evidence.json --rationale "Why this rule is satisfied."`;
400
+ }
401
+ if (command === "explain") {
402
+ return `${packageName} explain
403
+
404
+ Explain one compliance rule.
405
+
406
+ Usage:
407
+ vise explain sdk.init.at-startup`;
408
+ }
409
+ if (command === "status") {
410
+ return `${packageName} status
411
+
412
+ Print a compact compliance summary.
413
+
414
+ Usage:
415
+ vise status [repoPath]`;
416
+ }
417
+ return `${packageName}
418
+
419
+ Skill-guided deterministic CLI for social.plus SDK integration assistance.
420
+
421
+ Usage:
422
+ vise mcp Start the stdio MCP server
423
+ vise search-docs "query" Search hosted social.plus docs
424
+ vise get-doc-page "path" Read a canonical docs page
425
+ vise install-skill --target codex Install bundled skill guidance
426
+ vise print-skill Print bundled skill markdown
427
+ vise inspect [repoPath] Inspect platform and design signals
428
+ vise plan [repoPath] --request "..." Create an implementation plan
429
+ vise init [repoPath] --request "..." Initialize compliance sidecar
430
+ vise check [repoPath] Check compliance contract
431
+ vise sync [repoPath] Persist deterministic-pass evidence
432
+ vise attest [repoPath] --rule ... Record a compliance attestation
433
+ vise explain <ruleId> Explain one compliance rule
434
+ vise status [repoPath] Print compliance summary
435
+ vise validate [repoPath] Validate setup and common risks
436
+ vise run-sensors [repoPath] Run detected project sensors
437
+ vise doctor Print install diagnostics
438
+ vise --help Show this help
439
+ vise --version Show package version
440
+
441
+ Compatibility aliases:
442
+ spf, social-plus-vise, foundry, social-plus-foundry, foundry-mcp
443
+
444
+ MCP config:
445
+ {
446
+ "mcpServers": {
447
+ "social-plus": {
448
+ "command": "vise",
449
+ "args": ["mcp"]
450
+ }
451
+ }
452
+ }
453
+
454
+ Customers do not need environment variables. Maintainers may set
455
+ SOCIAL_PLUS_DOCS_ROOT to test against a local social-plus-docs checkout.`;
456
+ }
457
+ async function printToolResult(tool, input) {
458
+ const result = await tool.call(input);
459
+ console.log(result.content.map((item) => item.text).join("\n"));
460
+ }
461
+ async function installSkill(args) {
462
+ const source = skillSourceDir();
463
+ const instructionInstall = instructionInstallDestination(args);
464
+ if (instructionInstall) {
465
+ return installInstructionFile(instructionInstall, args);
466
+ }
467
+ const destination = skillInstallDestination(args);
468
+ const force = hasFlag(args, "force");
469
+ const installedFiles = await copyDirectory(source, destination, force);
470
+ return {
471
+ status: installedFiles.length > 0 ? "installed" : "already-current",
472
+ skill: bundledSkillName,
473
+ source,
474
+ destination,
475
+ force,
476
+ installedFiles,
477
+ nextStep: "Restart or reload the host AI coding tool so it discovers the installed skill.",
478
+ };
479
+ }
480
+ async function installInstructionFile(target, args) {
481
+ const force = hasFlag(args, "force");
482
+ const source = path.join(skillSourceDir(), "SKILL.md");
483
+ const content = await readFile(source, "utf8");
484
+ if (await fileExists(target.destination)) {
485
+ const existing = await readFile(target.destination, "utf8");
486
+ if (existing === content) {
487
+ return {
488
+ status: "already-current",
489
+ skill: bundledSkillName,
490
+ host: target.host,
491
+ source,
492
+ destination: target.destination,
493
+ force,
494
+ installedFiles: [],
495
+ nextStep: "Restart or reload the host AI coding tool so it discovers the updated project instructions.",
496
+ };
497
+ }
498
+ if (!force) {
499
+ throw new Error(`Instruction file already exists with different content: ${target.destination}. Re-run with --force to overwrite.`);
500
+ }
501
+ }
502
+ await mkdir(path.dirname(target.destination), { recursive: true });
503
+ await writeFile(target.destination, content, "utf8");
504
+ return {
505
+ status: "installed",
506
+ skill: bundledSkillName,
507
+ host: target.host,
508
+ source,
509
+ destination: target.destination,
510
+ force,
511
+ installedFiles: [target.destination],
512
+ nextStep: "Restart or reload the host AI coding tool so it discovers the updated project instructions.",
513
+ };
514
+ }
515
+ function instructionInstallDestination(args) {
516
+ const target = flagValue(args, "target");
517
+ if (target !== "cursor-rules" && target !== "cursor-rule") {
518
+ return undefined;
519
+ }
520
+ const repoRoot = path.resolve(positionalRepoPath(args));
521
+ return {
522
+ host: "cursor-rules",
523
+ destination: path.join(repoRoot, ".cursor", "rules", "social-plus-vise.mdc"),
524
+ };
525
+ }
526
+ function skillInstallDestination(args) {
527
+ const destArg = flagValue(args, "dest") ?? flagValue(args, "destination") ?? flagValue(args, "path");
528
+ if (destArg) {
529
+ const resolved = path.resolve(expandHome(destArg));
530
+ return path.basename(resolved) === bundledSkillName ? resolved : path.join(resolved, bundledSkillName);
531
+ }
532
+ const target = flagValue(args, "target") ?? "codex";
533
+ if (target === "codex") {
534
+ return path.join(os.homedir(), ".codex", "skills", bundledSkillName);
535
+ }
536
+ if (target === "claude") {
537
+ return path.join(os.homedir(), ".claude", "skills", bundledSkillName);
538
+ }
539
+ if (target === "claude-project") {
540
+ return path.resolve(positionalRepoPath(args), ".claude", "skills", bundledSkillName);
541
+ }
542
+ if (target === "agents" || target === "project") {
543
+ return path.resolve(positionalRepoPath(args), ".agents", "skills", bundledSkillName);
544
+ }
545
+ if (target === "cursor") {
546
+ return path.resolve(positionalRepoPath(args), ".cursor", "skills", bundledSkillName);
547
+ }
548
+ if (target === "vscode" || target === "copilot") {
549
+ return path.resolve(positionalRepoPath(args), ".github", "skills", bundledSkillName);
550
+ }
551
+ if (target === "custom") {
552
+ throw new Error("install-skill --target custom requires --dest <skillsRoot or exact skill directory>.");
553
+ }
554
+ throw new Error(`Unsupported skill target: ${target}. Supported targets: codex, claude, claude-project, agents, cursor, cursor-rules, vscode, copilot, custom.`);
555
+ }
556
+ function skillPathResult() {
557
+ return {
558
+ skill: bundledSkillName,
559
+ source: skillSourceDir(),
560
+ files: ["SKILL.md"],
561
+ installExamples: [
562
+ "vise install-skill --target codex",
563
+ "vise install-skill --target claude",
564
+ "vise install-skill --target claude-project .",
565
+ "vise install-skill --target agents .",
566
+ "vise install-skill --target cursor .",
567
+ "vise install-skill --target cursor-rules .",
568
+ "vise install-skill --target vscode .",
569
+ "vise install-skill --target copilot .",
570
+ "vise install-skill --dest ~/.codex/skills",
571
+ "vise print-skill",
572
+ ],
573
+ };
574
+ }
575
+ function skillSourceDir() {
576
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
577
+ return path.resolve(moduleDir, "..", "skills", bundledSkillName);
578
+ }
579
+ async function copyDirectory(source, destination, force) {
580
+ const installedFiles = [];
581
+ await mkdir(destination, { recursive: true });
582
+ const entries = await readdir(source, { withFileTypes: true });
583
+ for (const entry of entries) {
584
+ const sourcePath = path.join(source, entry.name);
585
+ const destinationPath = path.join(destination, entry.name);
586
+ if (entry.isDirectory()) {
587
+ installedFiles.push(...(await copyDirectory(sourcePath, destinationPath, force)));
588
+ continue;
589
+ }
590
+ if (!entry.isFile()) {
591
+ continue;
592
+ }
593
+ if (await fileExists(destinationPath)) {
594
+ const [sourceContent, destinationContent] = await Promise.all([readFile(sourcePath, "utf8"), readFile(destinationPath, "utf8")]);
595
+ if (sourceContent === destinationContent) {
596
+ continue;
597
+ }
598
+ if (!force) {
599
+ throw new Error(`Skill file already exists with different content: ${destinationPath}. Re-run with --force to overwrite.`);
600
+ }
601
+ }
602
+ await copyFile(sourcePath, destinationPath);
603
+ installedFiles.push(destinationPath);
604
+ }
605
+ return installedFiles;
606
+ }
607
+ async function fileExists(filePath) {
608
+ try {
609
+ return (await stat(filePath)).isFile();
610
+ }
611
+ catch {
612
+ return false;
613
+ }
614
+ }
615
+ function expandHome(input) {
616
+ if (input === "~") {
617
+ return os.homedir();
618
+ }
619
+ if (input.startsWith("~/")) {
620
+ return path.join(os.homedir(), input.slice(2));
621
+ }
622
+ return input;
623
+ }
624
+ function isHelpRequest(args) {
625
+ return args.includes("--help") || args.includes("-h");
626
+ }
627
+ function assertOnlyKnownFlags(args, allowed, commandName) {
628
+ const allowedSet = new Set(allowed);
629
+ const unknown = [];
630
+ for (const arg of args) {
631
+ if (!arg.startsWith("--")) {
632
+ continue;
633
+ }
634
+ const flagName = arg.slice(2).split("=")[0];
635
+ if (!flagName) {
636
+ continue;
637
+ }
638
+ if (!allowedSet.has(flagName)) {
639
+ unknown.push(`--${flagName}`);
640
+ }
641
+ }
642
+ if (unknown.length === 0) {
643
+ return;
644
+ }
645
+ const allowedList = allowed.length > 0 ? `Allowed flags: ${allowed.map((flag) => `--${flag}`).join(", ")}.` : "This command takes no flags.";
646
+ throw new Error(`${commandName} does not accept ${unknown.join(", ")}. ${allowedList} Run \`vise ${commandName} --help\` for usage.`);
647
+ }
648
+ function ciCheckResult(result) {
649
+ return {
650
+ ...result,
651
+ ci: {
652
+ enabled: true,
653
+ passed: result.exitCode === 0,
654
+ exitCode: result.exitCode,
655
+ blockingResultStatuses: ["contract-drift", "blocked", "deterministic-failures", "needs-attestation"],
656
+ message: result.exitCode === 0 ? "Compliance green for CI." : `Compliance failed for CI with status: ${result.status}.`,
657
+ },
658
+ };
659
+ }
660
+ function positionalRepoPath(args) {
661
+ const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale"]);
662
+ for (let index = 0; index < args.length; index += 1) {
663
+ const arg = args[index];
664
+ if (!arg) {
665
+ continue;
666
+ }
667
+ if (arg.startsWith("--")) {
668
+ const flagName = arg.slice(2).split("=")[0];
669
+ if (!arg.includes("=") && flagsWithValues.has(flagName)) {
670
+ index += 1;
671
+ }
672
+ continue;
673
+ }
674
+ if (arg.startsWith("-")) {
675
+ continue;
676
+ }
677
+ return arg;
678
+ }
679
+ return ".";
680
+ }
681
+ function requiredPositionalText(args, message) {
682
+ const values = [];
683
+ const flagsWithValues = new Set(["request", "surface", "surface-path", "platform", "include", "timeout-ms", "query", "path", "limit", "answer", "target", "dest", "destination", "rule", "confidence", "signer", "identity", "evidence-file", "rationale"]);
684
+ for (let index = 0; index < args.length; index += 1) {
685
+ const arg = args[index];
686
+ if (!arg) {
687
+ continue;
688
+ }
689
+ if (arg.startsWith("--")) {
690
+ const flagName = arg.slice(2).split("=")[0];
691
+ if (!arg.includes("=") && flagsWithValues.has(flagName)) {
692
+ index += 1;
693
+ }
694
+ continue;
695
+ }
696
+ if (!arg.startsWith("-")) {
697
+ values.push(arg);
698
+ }
699
+ }
700
+ const text = values.join(" ").trim();
701
+ if (!text) {
702
+ throw new Error(message);
703
+ }
704
+ return text;
705
+ }
706
+ function flagValue(args, name) {
707
+ const equalsPrefix = `--${name}=`;
708
+ const equalsArg = args.find((arg) => arg.startsWith(equalsPrefix));
709
+ if (equalsArg) {
710
+ return equalsArg.slice(equalsPrefix.length);
711
+ }
712
+ const index = args.indexOf(`--${name}`);
713
+ const value = index >= 0 ? args[index + 1] : undefined;
714
+ return value && !value.startsWith("-") ? value : undefined;
715
+ }
716
+ function flagValues(args, name) {
717
+ const values = [];
718
+ const equalsPrefix = `--${name}=`;
719
+ for (let index = 0; index < args.length; index += 1) {
720
+ const arg = args[index];
721
+ if (arg?.startsWith(equalsPrefix)) {
722
+ values.push(arg.slice(equalsPrefix.length));
723
+ }
724
+ else if (arg === `--${name}`) {
725
+ const value = args[index + 1];
726
+ if (value && !value.startsWith("-")) {
727
+ values.push(value);
728
+ }
729
+ }
730
+ }
731
+ return values;
732
+ }
733
+ function requiredFlagValue(args, name, message) {
734
+ const value = flagValue(args, name);
735
+ if (!value) {
736
+ throw new Error(message);
737
+ }
738
+ return value;
739
+ }
740
+ function optionalNumberFlag(args, name) {
741
+ const value = flagValue(args, name);
742
+ if (!value) {
743
+ return undefined;
744
+ }
745
+ const number = Number(value);
746
+ if (!Number.isFinite(number)) {
747
+ throw new Error(`--${name} must be a number.`);
748
+ }
749
+ return number;
750
+ }
751
+ function hasFlag(args, name) {
752
+ return args.includes(`--${name}`);
753
+ }
754
+ function keyValueFlag(args, name) {
755
+ const pairs = flagValues(args, name);
756
+ const result = {};
757
+ for (const pair of pairs) {
758
+ const equalsIndex = pair.indexOf("=");
759
+ if (equalsIndex <= 0) {
760
+ throw new Error(`--${name} must be key=value (got "${pair}").`);
761
+ }
762
+ const key = pair.slice(0, equalsIndex).trim();
763
+ const value = pair.slice(equalsIndex + 1).trim();
764
+ if (!key || !value) {
765
+ throw new Error(`--${name} must be key=value (got "${pair}").`);
766
+ }
767
+ result[key] = value;
768
+ }
769
+ return result;
770
+ }
771
+ function requiredConfidence(args) {
772
+ const value = requiredFlagValue(args, "confidence", "attest requires --confidence high|medium|low.");
773
+ if (value === "high" || value === "medium" || value === "low") {
774
+ return value;
775
+ }
776
+ throw new Error("--confidence must be high, medium, or low.");
777
+ }
778
+ function requiredSigner(args) {
779
+ const value = requiredFlagValue(args, "signer", "attest requires --signer host-agent|human.");
780
+ if (value === "host-agent" || value === "human") {
781
+ return value;
782
+ }
783
+ throw new Error("--signer must be host-agent or human.");
784
+ }
785
+ async function evidenceFromArgs(args) {
786
+ const evidenceFile = flagValue(args, "evidence-file");
787
+ if (!evidenceFile) {
788
+ throw new Error("attest requires --evidence-file.");
789
+ }
790
+ const parsed = JSON.parse(await readFile(path.resolve(evidenceFile), "utf8"));
791
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
792
+ throw new Error("--evidence-file must contain a JSON object.");
793
+ }
794
+ return parsed;
795
+ }
796
+ function doctorResult() {
797
+ const nodeMajor = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
798
+ return {
799
+ package: packageName,
800
+ version: packageVersion,
801
+ status: nodeMajor >= 18 ? "ok" : "unsupported-node",
802
+ node: process.versions.node,
803
+ requiredNodeMajor: ">=18",
804
+ docsSource: process.env.SOCIAL_PLUS_DOCS_ROOT ? "local" : "hosted",
805
+ docsRoot: process.env.SOCIAL_PLUS_DOCS_ROOT,
806
+ docsBaseUrl: process.env.SOCIAL_PLUS_DOCS_BASE_URL ?? "https://learn.social.plus",
807
+ transport: "stdio",
808
+ tools: Array.from(tools.keys()),
809
+ };
810
+ }