tina4ruby 3.11.18 → 3.11.19
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.
- checksums.yaml +4 -4
- data/lib/tina4/dev_admin.rb +106 -0
- data/lib/tina4/docs.rb +636 -0
- data/lib/tina4/mcp.rb +15 -0
- data/lib/tina4/public/js/tina4-dev-admin.js +121 -121
- data/lib/tina4/public/js/tina4-dev-admin.min.js +121 -121
- data/lib/tina4/rack_app.rb +46 -1
- data/lib/tina4/response.rb +3 -0
- data/lib/tina4/version.rb +1 -1
- data/lib/tina4.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 913dfbf6820b99da3ff8f0b5a845fb23239736ccc1db30eb958d664fba6abe9f
|
|
4
|
+
data.tar.gz: c7c39ce01253b8dc60edc30550b004914ac9c4742ee2efabfdedab44dfe984f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c926a2b39b248a45112e3f323174049ccdb0955c07ce3491a9346c837776cca37707ce54ecca21320ad71599b8d537a78eedadf4e675cd8c2b71dcfa6415ca39
|
|
7
|
+
data.tar.gz: caf6b606d28154e7ed8ded6e2519cc4a45f10a9034644f15f82625eace4e2747dbc27d845dadf0f0881a1c180c38a465428976a7be6d7b1c52b4239f637fd793
|
data/lib/tina4/dev_admin.rb
CHANGED
|
@@ -316,9 +316,55 @@ module Tina4
|
|
|
316
316
|
Tina4::Env.is_truthy(ENV["TINA4_DEBUG"])
|
|
317
317
|
end
|
|
318
318
|
|
|
319
|
+
# Write `.tina4/mcp.json` so MCP-aware tools (Claude Code, Cursor) can
|
|
320
|
+
# auto-discover this project's live docs server. Idempotent — no-op if
|
|
321
|
+
# the file already matches the desired contents. Also appends `.tina4/`
|
|
322
|
+
# to the project's `.gitignore` if a git repo is present.
|
|
323
|
+
def auto_discover_mcp!(project_root: Dir.pwd)
|
|
324
|
+
return if @_mcp_auto_discovered
|
|
325
|
+
@_mcp_auto_discovered = true
|
|
326
|
+
return unless enabled?
|
|
327
|
+
|
|
328
|
+
port = ENV["TINA4_PORT"] || ENV["PORT"] || "7147"
|
|
329
|
+
url = "http://localhost:#{port}/__dev/api/mcp"
|
|
330
|
+
target_dir = File.join(project_root, ".tina4")
|
|
331
|
+
target = File.join(target_dir, "mcp.json")
|
|
332
|
+
payload = {
|
|
333
|
+
"mcpServers" => {
|
|
334
|
+
"tina4-live-docs" => {
|
|
335
|
+
"url" => url,
|
|
336
|
+
"description" => "Live API docs for this Tina4 project (framework + user code)",
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
}
|
|
340
|
+
begin
|
|
341
|
+
FileUtils.mkdir_p(target_dir)
|
|
342
|
+
existing = if File.file?(target)
|
|
343
|
+
(JSON.parse(File.read(target)) rescue {})
|
|
344
|
+
else
|
|
345
|
+
{}
|
|
346
|
+
end
|
|
347
|
+
if existing != payload
|
|
348
|
+
File.write(target, JSON.pretty_generate(payload))
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
gitignore = File.join(project_root, ".gitignore")
|
|
352
|
+
if File.directory?(File.join(project_root, ".git"))
|
|
353
|
+
current = File.file?(gitignore) ? File.read(gitignore) : ""
|
|
354
|
+
unless current.lines.map(&:strip).include?(".tina4/") || current.lines.map(&:strip).include?(".tina4")
|
|
355
|
+
prefix = (!current.empty? && !current.end_with?("\n")) ? "\n" : ""
|
|
356
|
+
File.write(gitignore, "#{current}#{prefix}.tina4/\n")
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
rescue StandardError => e
|
|
360
|
+
Tina4::Log.warning("auto_discover_mcp! failed: #{e.message}") if defined?(Tina4::Log)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
319
364
|
# Handle a /__dev request; returns [status, headers, body] or nil if not a dev path
|
|
320
365
|
def handle_request(env)
|
|
321
366
|
return nil unless enabled?
|
|
367
|
+
auto_discover_mcp!
|
|
322
368
|
|
|
323
369
|
path = env["PATH_INFO"] || "/"
|
|
324
370
|
method = env["REQUEST_METHOD"]
|
|
@@ -548,6 +594,16 @@ module Tina4
|
|
|
548
594
|
when ["POST", "/__dev/api/scaffold/run"]
|
|
549
595
|
body = read_json_body(env) || {}
|
|
550
596
|
json_response(scaffold_run(body))
|
|
597
|
+
when ["GET", "/__dev/api/docs/search"]
|
|
598
|
+
json_response(docs_search_payload(env))
|
|
599
|
+
when ["GET", "/__dev/api/docs/class"]
|
|
600
|
+
json_response(docs_class_payload(query_param(env, "name")))
|
|
601
|
+
when ["GET", "/__dev/api/docs/method"]
|
|
602
|
+
json_response(docs_method_payload(query_param(env, "class"), query_param(env, "name")))
|
|
603
|
+
when ["GET", "/__dev/api/docs/index"]
|
|
604
|
+
json_response(docs_index_payload(query_param(env, "source")))
|
|
605
|
+
when ["GET", "/__dev/api/docs/.well-known.json"]
|
|
606
|
+
json_response(docs_well_known_payload)
|
|
551
607
|
when ["GET", "/__dev/api/graphql/schema"]
|
|
552
608
|
begin
|
|
553
609
|
gql = Tina4::GraphQL.new
|
|
@@ -1286,6 +1342,56 @@ module Tina4
|
|
|
1286
1342
|
{ ok: false, error: "unknown kind: #{kind}" }
|
|
1287
1343
|
end
|
|
1288
1344
|
end
|
|
1345
|
+
|
|
1346
|
+
# ── Live Docs (Live API RAG) ─────────────────────────────────
|
|
1347
|
+
|
|
1348
|
+
def docs_search_payload(env)
|
|
1349
|
+
q = (query_param(env, "q") || "").to_s
|
|
1350
|
+
k = (query_param(env, "k") || "5").to_i
|
|
1351
|
+
source = (query_param(env, "source") || "all").to_s
|
|
1352
|
+
include_private = %w[1 true yes].include?((query_param(env, "include_private") || "").to_s.downcase)
|
|
1353
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
1354
|
+
results = Tina4::Docs.cached(Dir.pwd).search(
|
|
1355
|
+
q, k: k, source: source, include_private: include_private
|
|
1356
|
+
)
|
|
1357
|
+
took_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000.0).round(2)
|
|
1358
|
+
{ ok: true, query: q, results: results, took_ms: took_ms }
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1361
|
+
def docs_class_payload(name)
|
|
1362
|
+
spec = Tina4::Docs.cached(Dir.pwd).class_spec(name.to_s)
|
|
1363
|
+
return { ok: false, error: "class not found: #{name}" } if spec.nil?
|
|
1364
|
+
{ ok: true, class: spec }
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
def docs_method_payload(class_fqn, name)
|
|
1368
|
+
spec = Tina4::Docs.cached(Dir.pwd).method_spec(class_fqn.to_s, name.to_s)
|
|
1369
|
+
return { ok: false, error: "method not found: #{class_fqn}##{name}" } if spec.nil?
|
|
1370
|
+
{ ok: true, method: spec }
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
def docs_index_payload(source)
|
|
1374
|
+
idx = Tina4::Docs.cached(Dir.pwd).index
|
|
1375
|
+
idx = idx.select { |e| e[:source] == source } if source && %w[framework user vendor].include?(source.to_s)
|
|
1376
|
+
{ ok: true, count: idx.size, entries: idx }
|
|
1377
|
+
end
|
|
1378
|
+
|
|
1379
|
+
def docs_well_known_payload
|
|
1380
|
+
{
|
|
1381
|
+
ok: true,
|
|
1382
|
+
service: "tina4-live-docs",
|
|
1383
|
+
version: Tina4::VERSION,
|
|
1384
|
+
framework: "tina4-ruby",
|
|
1385
|
+
endpoints: {
|
|
1386
|
+
search: "/__dev/api/docs/search?q=<query>&k=<int>&source=<framework|user|all>&include_private=<bool>",
|
|
1387
|
+
class: "/__dev/api/docs/class?name=<fqn>",
|
|
1388
|
+
method: "/__dev/api/docs/method?class=<fqn>&name=<method>",
|
|
1389
|
+
index: "/__dev/api/docs/index?source=<framework|user|all>",
|
|
1390
|
+
},
|
|
1391
|
+
mcp: { url: "/__dev/api/mcp", tools: %w[api_search api_class api_method] },
|
|
1392
|
+
spec: "https://tina4.com — Live API RAG plan/v3/22-LIVE-API-RAG.md",
|
|
1393
|
+
}
|
|
1394
|
+
end
|
|
1289
1395
|
end
|
|
1290
1396
|
end
|
|
1291
1397
|
end
|