@niroai/niro 0.16.0 → 0.18.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.
Files changed (177) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +123 -302
  3. package/bin/niro.js +12 -1
  4. package/docs/agent-antigravity.md +37 -0
  5. package/docs/agent-claude-code.md +48 -0
  6. package/docs/agent-claude-desktop.md +47 -0
  7. package/docs/agent-cline.md +42 -0
  8. package/docs/agent-codex.md +45 -0
  9. package/docs/agent-copilot-cli.md +42 -0
  10. package/docs/agent-cursor.md +44 -0
  11. package/docs/agent-factory.md +42 -0
  12. package/docs/agent-gemini-cli.md +42 -0
  13. package/docs/agent-goose.md +42 -0
  14. package/docs/agent-kiro.md +37 -0
  15. package/docs/agent-opencode.md +37 -0
  16. package/docs/agent-vscode.md +45 -0
  17. package/docs/agent-windsurf.md +38 -0
  18. package/docs/answer-shapes.md +69 -0
  19. package/docs/builds-and-updates.md +68 -0
  20. package/docs/cloud-README.md +372 -0
  21. package/docs/configuring-a-folder.md +51 -0
  22. package/docs/connect-ci.md +24 -0
  23. package/docs/connect-overview.md +20 -0
  24. package/docs/connect-review-bots.md +17 -0
  25. package/docs/connect-team-server.md +12 -0
  26. package/docs/connect-terminal.md +49 -0
  27. package/docs/connect-web-agents.md +9 -0
  28. package/docs/connect-your-ai-assistant.md +59 -175
  29. package/docs/coverage-audit-and-consent.md +51 -0
  30. package/docs/evaluate-what-niro-did.md +271 -0
  31. package/docs/faq-assistants-and-mcp.md +122 -0
  32. package/docs/faq-feedback-and-receipts.md +33 -0
  33. package/docs/faq-indexing.md +127 -0
  34. package/docs/faq-install-and-setup.md +103 -0
  35. package/docs/faq-licence-and-plans.md +86 -0
  36. package/docs/faq-login-and-account.md +69 -0
  37. package/docs/faq-offline.md +34 -0
  38. package/docs/faq-performance.md +33 -0
  39. package/docs/faq-privacy-and-security.md +113 -0
  40. package/docs/faq-uninstall.md +47 -0
  41. package/docs/faq-updates.md +59 -0
  42. package/docs/faq-worktrees-and-branches.md +59 -0
  43. package/docs/faq-wrong-or-missing-answers.md +275 -0
  44. package/docs/hosted-only.md +33 -0
  45. package/docs/how-the-graph-works.md +47 -0
  46. package/docs/index.md +157 -0
  47. package/docs/licence-and-plans.md +72 -0
  48. package/docs/lifecycle-debug.md +48 -0
  49. package/docs/lifecycle-onboarding.md +32 -0
  50. package/docs/lifecycle-overview.md +77 -0
  51. package/docs/lifecycle-plan-and-design.md +44 -0
  52. package/docs/lifecycle-review.md +30 -0
  53. package/docs/lifecycle-security-and-privacy.md +35 -0
  54. package/docs/lifecycle-test.md +28 -0
  55. package/docs/lifecycle-write-code.md +35 -0
  56. package/docs/llms-full.txt +8015 -0
  57. package/docs/llms.txt +126 -0
  58. package/docs/playbook-add-a-feature.md +108 -0
  59. package/docs/playbook-change-safely.md +93 -0
  60. package/docs/playbook-data-and-events.md +107 -0
  61. package/docs/playbook-debug-after-a-change.md +96 -0
  62. package/docs/playbook-debug-config.md +95 -0
  63. package/docs/playbook-debug-exception.md +124 -0
  64. package/docs/playbook-debug-failing-service-call.md +119 -0
  65. package/docs/playbook-debug-slow-path.md +80 -0
  66. package/docs/playbook-debug-wrong-value.md +137 -0
  67. package/docs/playbook-onboard.md +95 -0
  68. package/docs/playbook-review-a-change.md +72 -0
  69. package/docs/playbook-test-a-change.md +73 -0
  70. package/docs/reference-cli.md +113 -0
  71. package/docs/reference-config-files.md +42 -0
  72. package/docs/reference-engine-commands.md +56 -0
  73. package/docs/reference-environment-variables.md +89 -0
  74. package/docs/reference-exit-codes.md +67 -0
  75. package/docs/reference-limits.md +75 -0
  76. package/docs/reference-mcp-tools.md +326 -0
  77. package/docs/reference-messages.md +180 -0
  78. package/docs/secrets-and-masking.md +48 -0
  79. package/docs/supported-c.md +130 -0
  80. package/docs/supported-cpp.md +157 -0
  81. package/docs/supported-csharp.md +211 -0
  82. package/docs/supported-go.md +258 -0
  83. package/docs/supported-java.md +226 -0
  84. package/docs/supported-javascript.md +223 -0
  85. package/docs/supported-kotlin.md +124 -0
  86. package/docs/supported-overview.md +89 -0
  87. package/docs/supported-php.md +157 -0
  88. package/docs/supported-python.md +224 -0
  89. package/docs/supported-ruby.md +162 -0
  90. package/docs/supported-rust.md +150 -0
  91. package/docs/supported-swift.md +133 -0
  92. package/docs/supported-typescript.md +220 -0
  93. package/docs/tokens-speed-and-accuracy.md +99 -0
  94. package/docs/troubleshooting.md +42 -0
  95. package/docs/uninstall.md +74 -0
  96. package/docs/what-leaves-your-machine.md +71 -0
  97. package/docs/where-things-are-stored.md +59 -0
  98. package/docs/worktrees-and-branches.md +55 -0
  99. package/package.json +5 -3
  100. package/src/commands/add-repo.js +5 -0
  101. package/src/commands/auditSend.js +67 -0
  102. package/src/commands/build.js +6 -0
  103. package/src/commands/docsSend.js +73 -0
  104. package/src/commands/engine.js +120 -0
  105. package/src/commands/engineRoutes.js +105 -0
  106. package/src/commands/guide.js +254 -0
  107. package/src/commands/init.js +16 -7
  108. package/src/commands/initLocal.js +96 -0
  109. package/src/commands/journalSend.js +47 -0
  110. package/src/commands/login.js +89 -12
  111. package/src/commands/mcp.js +22 -6
  112. package/src/commands/plan.js +99 -0
  113. package/src/commands/repoPush.js +33 -0
  114. package/src/commands/requestIntegration.js +38 -0
  115. package/src/commands/share.js +56 -0
  116. package/src/commands/start.js +451 -371
  117. package/src/commands/status.js +57 -0
  118. package/src/commands/takeover.js +6 -0
  119. package/src/commands/uninstall.js +180 -0
  120. package/src/commands/upgrade.js +38 -8
  121. package/src/commands/watch.js +420 -6
  122. package/src/index.js +174 -3
  123. package/src/lib/agentApi.js +32 -1
  124. package/src/lib/api.js +20 -6
  125. package/src/lib/auditUpload.js +293 -0
  126. package/src/lib/branchDrift.js +149 -0
  127. package/src/lib/choose.js +116 -0
  128. package/src/lib/cloudGuard.js +74 -0
  129. package/src/lib/credentials.js +2 -2
  130. package/src/lib/docsMarked.js +55 -0
  131. package/src/lib/docsState.js +213 -0
  132. package/src/lib/docsUpload.js +481 -0
  133. package/src/lib/engine.js +619 -0
  134. package/src/lib/folderSync.js +2 -1
  135. package/src/lib/graphCheck.js +40 -0
  136. package/src/lib/guide.js +293 -0
  137. package/src/lib/guideSearch.js +252 -0
  138. package/src/lib/integrationRequest.js +89 -0
  139. package/src/lib/journalUpload.js +147 -0
  140. package/src/lib/languages.js +129 -0
  141. package/src/lib/lease.js +375 -0
  142. package/src/lib/localBuild.js +285 -0
  143. package/src/lib/localEngine.js +210 -0
  144. package/src/lib/localOnboard.js +124 -0
  145. package/src/lib/localWorktrees.js +243 -0
  146. package/src/lib/machineCheck.js +58 -0
  147. package/src/lib/mcpClients.js +10 -1
  148. package/src/lib/migrationNotice.js +118 -0
  149. package/src/lib/modelFetch.js +153 -0
  150. package/src/lib/nestedOutput.js +23 -9
  151. package/src/lib/onboardingLog.js +34 -0
  152. package/src/lib/planUsage.js +165 -0
  153. package/src/lib/progressBar.js +46 -0
  154. package/src/lib/prompt.js +1 -1
  155. package/src/lib/redact.js +155 -0
  156. package/src/lib/release.js +33 -0
  157. package/src/lib/repoPush.js +495 -0
  158. package/src/lib/secret.js +1 -1
  159. package/src/lib/service.js +372 -63
  160. package/src/lib/shareCode.js +367 -0
  161. package/src/lib/shareExclusions.js +114 -0
  162. package/src/lib/shareFiles.js +129 -0
  163. package/src/lib/spinner.js +9 -5
  164. package/src/lib/watchDaemon.js +33 -10
  165. package/src/lib/watchState.js +12 -1
  166. package/src/lib/windowsPaths.js +67 -0
  167. package/src/mcp/lineCheck.js +382 -0
  168. package/src/mcp/lineName.js +92 -0
  169. package/src/mcp/localBackend.js +751 -0
  170. package/src/mcp/localMode.js +383 -0
  171. package/src/mcp/localSkill.js +189 -0
  172. package/src/mcp/server.js +610 -79
  173. package/src/mcp/setup.js +519 -131
  174. package/src/mcp/shellSearchHook.js +509 -0
  175. package/src/mcp/teardown.js +173 -91
  176. package/src/mcp/usingNiro.js +39 -0
  177. package/docs/working-on-branches.md +0 -162
@@ -0,0 +1,133 @@
1
+ # Supported in Swift
2
+
3
+ <!-- GENERATED by scripts/gen-supported-docs.js. Do not edit by hand: run the script again. -->
4
+ <!-- source: catalog/swift.json; catalog/swift.structure.json -->
5
+ <!-- measured: coverage corpus 1bb4ebb, niro-local 2b30d4fd -->
6
+
7
+ This page lists the Swift libraries, frameworks and build tools Niro was tested against, and whether it recognised each one.
8
+
9
+ **What the marks mean.**
10
+
11
+ - **Supported:** Niro recognised every way of using it that the test code tries.
12
+ - **Partly:** Niro recognised some of those ways, or recognised one only in part.
13
+ - **Not yet:** Niro did not recognise it in the test code.
14
+
15
+ The test code is made up for the purpose: one small service per library, with the names invented. So "supported" means Niro recognised it there. It is good evidence, but it is not a promise about every codebase. If Niro misses something in your code, the coverage audit reports the gap on its own (see [coverage-audit-and-consent.md](coverage-audit-and-consent.md)).
16
+ <!-- source: niro-coverage-corpus README.md ("Synthetic test code ... All names are made up"); catalog status merged pairs one to one with measured found -->
17
+
18
+ The test catalog also holds 5 look-alike cases for Swift: code that Niro must NOT take for a route, a call or a store. They test that Niro ignores the look-alike, so they are left out of this page and its counts.
19
+ <!-- source: catalog/swift.json and catalog/swift.structure.json, rows marked as traps (scripts/gen-supported-docs.js isTrap) -->
20
+
21
+ ## Web frameworks and HTTP servers (the routes your code serves)
22
+
23
+ 1 supported, 1 partly, 4 not yet.
24
+
25
+ | Name | Status | Ways of using it that were tested |
26
+ |---|---|---|
27
+ | Hummingbird | partly | yes: let router = Router(); router.get("path") { request, context in }<br>yes: router.post("path", use: handler)<br>yes: router.group("api").get("x/:id") { .. }<br>no: RouteCollection(context:).get(":id", use:) added with router.addRoutes(_:atPath:) |
28
+ | Kitura | not yet | no: router.get("/path") { request, response, next in } |
29
+ | Perfect | not yet | no: routes.add(method: .get, uri: "/path") { request, response in } |
30
+ | Swifter | not yet | no: server["/path"] = { request in } and server.GET["/path"] = { }<br>no: server.POST["/path"] = { } |
31
+ | SwiftNIO | not yet | no: ChannelInboundHandler with switch (head.method, head.uri) cases |
32
+ | Vapor | supported | yes: app.get("path") { req in ... } with a trailing closure<br>yes: app.post("path", use: handler) and path segments as separate arguments<br>yes: app.on(.PATCH, "path", use: handler)<br>yes: let api = app.grouped("api", "v1"); api.get(..)<br>yes: app.group("admin") { admin in admin.post(..) }<br>yes: struct X: RouteCollection { func boot(routes:) { routes.grouped("x").get(use: h) } } registered with app.register(collection:)<br>yes: invoices.group(":id") { invoice in invoice.delete(use: delete) } inside boot(routes:) |
33
+
34
+ ## The language's own HTTP server
35
+
36
+ 0 supported, 0 partly, 1 not yet.
37
+
38
+ | Name | Status | Ways of using it that were tested |
39
+ |---|---|---|
40
+ | Network.framework NWListener | not yet | no: NWListener(using: .tcp, on: port) with newConnectionHandler reading the request line |
41
+
42
+ ## HTTP client libraries (the calls your code makes to other services)
43
+
44
+ 1 supported, 2 partly, 2 not yet.
45
+
46
+ | Name | Status | Ways of using it that were tested |
47
+ |---|---|---|
48
+ | Alamofire | partly | yes: AF.request("url").response { }<br>yes: AF.request(url, method: .post, parameters:, encoder:)<br>no: let session = Session(configuration:); session.request(url)<br>partly: AF.upload(data, to: url)<br>yes: AF.download(url)<br>yes: try await AF.request(url).serializingString().value |
49
+ | AsyncHTTPClient | partly | partly: var r = HTTPClientRequest(url:); r.method = .POST; HTTPClient.shared.execute(r, timeout:)<br>yes: HTTPClient(eventLoopGroupProvider:).get(url:).wait() |
50
+ | Moya | not yet | no: enum API: TargetType with baseURL, path and method in switch branches; MoyaProvider<API>().request(.case) |
51
+ | swift-openapi-generator | not yet | no: Client(serverURL:transport: URLSessionTransport()) then client.<operationId>() from openapi.yaml |
52
+ | Vapor | supported | yes: Vapor's client: req.client.get("absolute url")<br>yes: req.client.post(URI(string: url)) { try $0.content.encode(..) } |
53
+
54
+ ## The language's own HTTP client
55
+
56
+ 0 supported, 1 partly, 1 not yet.
57
+
58
+ | Name | Status | Ways of using it that were tested |
59
+ |---|---|---|
60
+ | Network.framework NWConnection | not yet | no: NWConnection(host:port:using: .tcp) then send(content: "GET /path HTTP/1.1...") |
61
+ | URLSession | partly | yes: URLSession.shared.dataTask(with: URL(string:)!) { } .resume()<br>yes: try await URLSession.shared.data(from: URL)<br>yes: var request = URLRequest(url:); request.httpMethod = "POST"; request.httpBody = ..; data(for: request)<br>yes: try await URLSession.shared.upload(for: request, from: data)<br>yes: URLSession.shared.uploadTask(with: request, from: data) { }.resume()<br>yes: try await URLSession.shared.download(from: URL)<br>yes: URLSession.shared.downloadTask(with: URL) { }.resume()<br>yes: try await URLSession.shared.bytes(from: URL)<br>yes: let session = URLSession(configuration: .ephemeral); session.data(from:)<br>partly: base URL from ProcessInfo environment with a literal fallback, joined with +<br>partly: client class holding session and baseURL; baseURL.appendingPathComponent(path); path given by the caller |
62
+
63
+ ## gRPC
64
+
65
+ 1 supported, 1 partly, 1 not yet.
66
+
67
+ | Name | Status | Ways of using it that were tested |
68
+ |---|---|---|
69
+ | connect-swift | not yet | no: Connect: ProtocolClient(httpClient:config:) and generated <Pkg>_<S>Client(client:).<rpc>(request:) |
70
+ | grpc-swift | partly | yes: server v1, form implements: class X: <Pkg>_<S>Provider with func <rpc>(request:context:) -> EventLoopFuture<br>yes: registration: Server.insecure(group:).withServiceProviders([X()]).bind(host:port:)<br>yes: server v1 async, form implements: class X: <Pkg>_<S>AsyncProvider with async throws methods<br>no: client v1, form constructor: <Pkg>_<S>NIOClient(channel:) then client.<rpc>(req).response<br>no: client v1, form constructor: <Pkg>_<S>AsyncClient(channel:) then try await client.<rpc>(req) |
71
+ | grpc-swift-2 | supported | yes: server v2, form implements: struct X: <Pkg>_<S>.SimpleServiceProtocol; GRPCServer(transport:services:)<br>yes: client v2, form constructor: <Pkg>_<S>.Client(wrapping: grpcClient) inside withGRPCClient |
72
+
73
+ ## Databases and data stores
74
+
75
+ 3 supported, 3 partly, 7 not yet.
76
+
77
+ | Name | Status | Ways of using it that were tested |
78
+ |---|---|---|
79
+ | CloudKit | supported | yes: CKRecord(recordType: "X") saved with database.save; CKQuery(recordType:) |
80
+ | Core Data | not yet | no: NSFetchRequest<T>(entityName: "X") then context.fetch(request)<br>no: Order.fetchRequest() from a NSManagedObject subclass, then context.fetch<br>no: NSEntityDescription.insertNewObject(forEntityName: "X", into: context); context.save()<br>no: context.execute(NSBatchDeleteRequest(fetchRequest: NSFetchRequest(entityName:))) |
81
+ | FileManager | supported | yes: FileManager.default.createFile(atPath:contents:attributes:) / contents(atPath:) |
82
+ | Firebase Firestore | not yet | no: db.collection("x").document(id).setData(..) / .getDocuments()<br>no: db.collection("x").addDocument(data:) |
83
+ | Fluent | not yet | no: Model.query(on: db).filter(..).all() with static let schema = "table"<br>no: model.save(on: db)<br>no: (db as? SQLDatabase).raw("SQL").run() |
84
+ | GRDB | not yet | no: db.execute(sql:arguments:) inside dbQueue.write { db in }<br>no: Row.fetchAll(db, sql:)<br>no: record type with databaseTableName; Invoice.fetchAll(db), invoice.insert(db) |
85
+ | MongoKitten | not yet | no: db["collection"].insert(..) / .find(..) |
86
+ | PostgresNIO | partly | yes: PostgresClient.query("SQL with \(interpolation)", logger:)<br>partly: client.withConnection { connection in connection.query(..) }<br>yes: shared table: main writes shipments that services/fluent reads |
87
+ | RealmSwift | partly | yes: realm.objects(Type.self)<br>partly: try realm.write { realm.add(instance) } |
88
+ | RediStack | not yet | no: redis.set("key", to: v) / redis.get("key", as:) |
89
+ | SQLite.swift | supported | yes: let carts = Table("carts"); db.run(carts.insert(..)); db.prepare(carts)<br>yes: db.execute("raw SQL") |
90
+ | SwiftData | not yet | no: context.fetch(FetchDescriptor<Model>(predicate:)) on a @Model class<br>no: modelContext.insert(Model(..))<br>no: SwiftUI @Query var items: [Model] |
91
+ | UserDefaults | partly | yes: UserDefaults.standard.set(v, forKey: "k") / .string(forKey:)<br>no: UserDefaults(suiteName: "group.x")?.set(v, forKey:)<br>no: SwiftUI property wrapper @AppStorage("key") |
92
+
93
+ ## Messaging and events (queues, topics, streams)
94
+
95
+ 6 supported, 1 partly, 4 not yet.
96
+
97
+ | Name | Status | Ways of using it that were tested |
98
+ |---|---|---|
99
+ | CocoaMQTT | supported | yes: mqtt.publish("topic", withString:)<br>yes: mqtt.subscribe("topic", qos:) with didReceiveMessage<br>yes: shared MQTT topic: main publishes warehouse/temperature |
100
+ | Combine | supported | yes: in-process: subject.send(value) on a PassthroughSubject<br>yes: in-process: CurrentValueSubject.send(value) |
101
+ | DistributedNotificationCenter | not yet | no: macOS cross-process: DistributedNotificationCenter.default().postNotificationName(..) / addObserver(forName:) |
102
+ | nats.swift | supported | yes: nats.subscribe(subject:) / nats.publish(data, subject:)<br>yes: shared NATS subject: main publishes shipments.created |
103
+ | NotificationCenter | supported | yes: NotificationCenter.default.post(name: Notification.Name("X"), object:)<br>yes: post(name: .orderPlaced) where .orderPlaced is declared in an extension of Notification.Name<br>yes: addObserver(forName:object:queue:) { note in }<br>yes: addObserver(self, selector: #selector(f), name: Notification.Name("X"), object:)<br>yes: NotificationCenter.default.publisher(for: name).sink { }<br>yes: for await _ in NotificationCenter.default.notifications(named:) |
104
+ | rabbitmq-nio | supported | yes: channel.basicPublish(from:exchange:routingKey:) / basicConsume(queue:) |
105
+ | RediStack | supported | yes: redis.publish(message, to: "channel")<br>yes: redis.subscribe(to: "channel") { channel, message in }<br>yes: shared Redis channel: main publishes stock-updates |
106
+ | Soto (AWS SNS) | not yet | no: sns.publish(.init(message:topicArn:)) |
107
+ | Soto (AWS SQS) | not yet | no: sqs.sendMessage(.init(messageBody:queueUrl:))<br>no: sqs.receiveMessage(.init(queueUrl:))<br>no: shared SQS queue URL: main sends to refunds |
108
+ | swift-kafka-client | partly | no: producer.send(KafkaProducerMessage(topic: "t", value:))<br>no: KafkaConsumerConfiguration(consumptionStrategy: .group(id:topics: ["t"])); for try await m in consumer.messages<br>partly: shared topic: main produces orders.placed that services/swift-kafka consumes |
109
+ | Vapor Queues | not yet | no: req.queue.dispatch(Job.self, payload); struct Job: AsyncJob { func dequeue } |
110
+
111
+ ## Build tools and package managers
112
+
113
+ 8 supported, 0 partly, 0 not yet.
114
+
115
+ | Name | Status | Ways of using it that were tested |
116
+ |---|---|---|
117
+ | Bazel rules_swift | supported | yes: MODULE.bazel + BUILD.bazel with swift_binary and swift_library, no Package.swift |
118
+ | Carthage | supported | yes: Cartfile; Carthage/Checkouts (sources) and Carthage/Build (frameworks) beside an Xcode project |
119
+ | CocoaPods | supported | yes: Podfile with a remote pod (vendored under Pods/) and a development pod by :path with a .podspec |
120
+ | SwiftPM | supported | yes: one Package.swift: executableTarget with main.swift, a library target, a testTarget<br>yes: products: .executable and .library; @main type instead of main.swift<br>yes: two app packages depend on a local library package by .package(path:)<br>yes: a .macro target (swift-syntax) and a build-tool .plugin under Plugins/, used by an executable |
121
+ | Tuist | supported | yes: Workspace.swift and Project.swift manifests (Swift code importing ProjectDescription) per project |
122
+ | Xcode project | supported | yes: ShopApp.xcodeproj with an app target, a framework folder and a unit-test folder; no Package.swift |
123
+ | Xcode workspace | supported | yes: Shop.xcworkspace referencing two .xcodeproj (app and framework) |
124
+ | XcodeGen | supported | yes: project.yml declaring an application target and a framework target by source folder |
125
+
126
+ ## Project and folder layouts
127
+
128
+ 1 supported, 1 partly, 0 not yet.
129
+
130
+ | Name | Status | Ways of using it that were tested |
131
+ |---|---|---|
132
+ | SwiftPM | supported | yes: targets with path: outside Sources/ (Server/Sources, Shared/Lib, Server/Tests)<br>yes: .build/ (checkouts and debug output), .swiftpm/ and DerivedData/ beside Package.swift, no .gitignore<br>yes: several SwiftPM services under services/<name>/ (code in Sources/, not src/), split by a root docker-compose.yml |
133
+ | Xcode project | partly | partly: one app target mixing Swift and Objective-C through a bridging header |
@@ -0,0 +1,220 @@
1
+ # Supported in TypeScript
2
+
3
+ <!-- GENERATED by scripts/gen-supported-docs.js. Do not edit by hand: run the script again. -->
4
+ <!-- source: catalog/typescript.json; catalog/typescript.structure.json -->
5
+ <!-- measured: coverage corpus 1bb4ebb, niro-local 2b30d4fd -->
6
+
7
+ This page lists the TypeScript libraries, frameworks and build tools Niro was tested against, and whether it recognised each one.
8
+
9
+ **What the marks mean.**
10
+
11
+ - **Supported:** Niro recognised every way of using it that the test code tries.
12
+ - **Partly:** Niro recognised some of those ways, or recognised one only in part.
13
+ - **Not yet:** Niro did not recognise it in the test code.
14
+
15
+ The test code is made up for the purpose: one small service per library, with the names invented. So "supported" means Niro recognised it there. It is good evidence, but it is not a promise about every codebase. If Niro misses something in your code, the coverage audit reports the gap on its own (see [coverage-audit-and-consent.md](coverage-audit-and-consent.md)).
16
+ <!-- source: niro-coverage-corpus README.md ("Synthetic test code ... All names are made up"); catalog status merged pairs one to one with measured found -->
17
+
18
+ The test catalog also holds 19 look-alike cases for TypeScript: code that Niro must NOT take for a route, a call or a store. They test that Niro ignores the look-alike, so they are left out of this page and its counts.
19
+ <!-- source: catalog/typescript.json and catalog/typescript.structure.json, rows marked as traps (scripts/gen-supported-docs.js isTrap) -->
20
+
21
+ ## Web frameworks and HTTP servers (the routes your code serves)
22
+
23
+ 45 supported, 3 partly, 4 not yet.
24
+
25
+ | Name | Status | Ways of using it that were tested |
26
+ |---|---|---|
27
+ | @adonisjs/core | partly | yes: router.get(path, handler) in start/routes.ts<br>no: router.resource('podcasts', Controller).only([...])<br>yes: router.group(() => {...}).prefix('/admin') |
28
+ | @analogjs/platform | supported | yes: src/server/routes/api/<path>.<verb>.ts: Analog API routes (Nitro), verb from the file name<br>yes: src/server/routes/api/<path>/[param].ts: a dynamic Analog API route, every verb |
29
+ | @angular/router | supported | yes: Routes array { path: 'quotes/:id', component }: client-side routes, NOT endpoints |
30
+ | @azure/functions (v3 programming model) | supported | yes: <FunctionName>/function.json with an httpTrigger binding: route (with {param}, {param:constraint}, {param?}) and methods, under host.json routePrefix (default api)<br>yes: function.json httpTrigger with no route: /api/<FunctionName>, the folder name |
31
+ | @azure/functions (v4) | supported | yes: app.http('name', { methods, route, handler }); served under /api<br>yes: app.get / app.post / app.put / app.patch / app.deleteRequest(name, handler or options): the verb is the call, the route is options.route or the name |
32
+ | @builder.io/qwik-city | supported | yes: src/routes/<path>/index.ts exporting onGet, onPost, ...: a Qwik City endpoint (only index files are routes)<br>yes: src/routes/<path>/[param]/index.ts: a dynamic Qwik City endpoint<br>yes: index.ts exporting onRequest: one handler for every method, here under a [...rest] folder<br>yes: src/routes/(group)/<path>/index.ts: a grouped folder is left out of the URL |
33
+ | @fastify/autoload | supported | yes: register(AutoLoad, { dir: 'routes' }): each folder's name prefixes the routes its plugin registers (dirNameRoutePrefix default true)<br>yes: routeParams: true: a folder named _param becomes :param in the prefix<br>yes: options: { prefix } on the AutoLoad registration, plus export const autoPrefix in the plugin file |
34
+ | @loopback/rest | not yet | no: lower-case @get('/x/{id}') and @post('/x') on methods |
35
+ | @nestjs/common | partly | yes: @Controller('prefix') class with @Get(), @Get(':id'), @Post()<br>yes: @Put(path), @Patch(path), @Delete(path) with stacked decorators<br>partly: app.setGlobalPrefix('api') in main.ts applies to every controller<br>partly: @Controller({ path, version }) with URI versioning |
36
+ | @oak/oak (Deno) | supported | yes: new Router().get().post() imported from jsr:@oak/oak |
37
+ | @react-router/dev (React Router v7 framework mode) | supported | yes: app/routes.ts config: route('api/stock/:sku', './api/stock.ts') and prefix('v2', [...]) name the path and the module |
38
+ | @react-router/fs-routes (React Router v7 framework mode) | supported | yes: app/routes.ts `export default flatRoutes()` and app/routes/<dotted>.ts resource routes (loader GET, action POST/PUT/PATCH/DELETE) |
39
+ | @remix-run/node | supported | yes: app/routes/<dotted.path>.tsx exporting loader (GET) and action (POST) |
40
+ | @remix-run/node (Remix v2) | supported | yes: a resource route: a route module with a loader and no default component, serving GET only<br>yes: <parent>._index.ts: the index route of a parent path<br>yes: <path>.$.ts: a splat route, params['*'] holds the rest of the URL<br>yes: _layout.<path>.ts: a leading underscore segment is a pathless layout, left out of the URL<br>yes: <segment>_.<rest>.ts: a trailing underscore opts out of the parent layout; the underscore is not in the URL<br>yes: ($param).<path>.ts: an optional segment, matching with and without it<br>yes: [.] or [.ext] in a file name keeps a literal dot in the URL<br>yes: <path>/route.ts: a folder route; other modules in the folder are not routes |
41
+ | @solidjs/start | supported | yes: src/routes/<path>.ts exporting GET, POST, ... (no default component): a SolidStart API route<br>yes: src/routes/<path>/[param].ts: a dynamic SolidStart API route<br>yes: src/routes/<path>/[...rest].ts: a catch-all SolidStart API route, verb bound by `export const GET = handler`<br>yes: src/routes/(group)/<path>/[[optional]].ts: a group folder and an optional parameter; one function bound to two verbs |
42
+ | @sveltejs/kit | supported | yes: src/routes/<path>/+server.ts exporting GET/POST/DELETE (const or function)<br>yes: src/routes/<path>/[param]/+server.ts: a dynamic segment<br>yes: src/routes/<path>/[...rest]/+server.ts: a rest parameter, which may match zero segments<br>yes: src/routes/<path>/[[optional]]/...: an optional parameter in the middle of the path<br>yes: src/routes/(group)/<path>/+server.ts: a group folder is left out of the URL<br>yes: src/routes/<path>/[id=matcher]/+server.ts: a parameter with a matcher from src/params<br>yes: +server.ts exporting `fallback`: serves every method with no handler of its own<br>yes: +page.server.ts `export const actions`: form actions answer POST at the page's path (default) or ?/name<br>yes: +server.ts beside +page.svelte in one folder (content negotiation): GET with Accept text/html goes to the page, others to +server |
43
+ | @tanstack/react-start | supported | yes: src/routes/<path>.ts: createFileRoute('/path')({ server: { handlers: { GET, POST } } }); a file may also hold a component<br>yes: src/routes/<path>/$param.ts, $.ts splat and [.] escape in a server route |
44
+ | @tanstack/start (before the Vite move) | supported | yes: app/routes/api/<path>.ts: export const APIRoute = createAPIFileRoute('/api/path')({ GET, POST }) |
45
+ | @trpc/server | not yet | no: t.router({ ns: t.router({ q: procedure.query(), m: procedure.mutation() }) }): GET /trpc/ns.q, POST /trpc/ns.m |
46
+ | astro | supported | yes: src/pages/<path>.ts exporting GET/POST APIRoute<br>yes: src/pages/<path>/[param].ts in server output: a dynamic endpoint; _file and _folder are excluded<br>yes: src/pages/<path>/[...path].ts: a rest parameter, which also matches the bare path<br>yes: src/pages/<name>.<ext>.ts: the output extension stays in the URL (feed.json.ts -> /feed.json), also after a parameter |
47
+ | AWS SAM template.yaml | supported | yes: Events: Type: HttpApi Properties: Path, Method; Handler names the export |
48
+ | aws-lambda (API Gateway v2) | not yet | no: switch (event.routeKey) { case 'GET /claims': ... } |
49
+ | bun (Bun.FileSystemRouter) | supported | yes: new Bun.FileSystemRouter({ style: 'nextjs', dir }) with router.match(req) in Bun.serve: Next.js-style file names under dir, no method chosen |
50
+ | Cloudflare Pages Functions | supported | yes: functions/<path>.ts exporting onRequest: every verb at the file's path<br>yes: exports onRequestGet, onRequestPost, onRequestPut, onRequestPatch, onRequestDelete, onRequestHead, onRequestOptions: the export name is the verb<br>yes: functions/index.ts serves / and functions/<dir>/index.ts serves /<dir><br>yes: functions/<dir>/[param].ts: one segment, value in context.params<br>yes: functions/<dir>/[[param]].ts: matches any depth, params.param is an array<br>yes: _routes.json in the build output folder: exclude wins over include, an excluded path never reaches a function |
51
+ | elysia | partly | yes: new Elysia().get().post() chained<br>no: app.group('/prefix', app => app.get(...)) |
52
+ | elysia-autoload | supported | yes: app.use(await autoload({ dir, prefix })): routes/<path>.ts default-exports (app) => app.get('', ...); [id].ts and [...].ts |
53
+ | Expo Router API routes (expo-router) | supported | yes: app/<path>+api.ts exporting GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS with web Request and Response<br>yes: app/<dir>/[param]+api.ts: one segment, passed as the second argument<br>yes: a (group) folder adds nothing to the URL<br>yes: app/<dir>/[...rest]+api.ts: catch-all |
54
+ | express | supported | yes: typed Router (Request<Params>, Response) mounted with app.use("/api", router) |
55
+ | express-file-routing | supported | yes: routes/<path>.ts exporting get, post, put, patch, del; mounted with app.use('/api', await router())<br>yes: a default export serves every method; [...path].ts is a catch-all (not the base path alone) |
56
+ | fastify | supported | yes: Fastify().withTypeProvider<T>().get(path, { schema }, h); fastify.post<{ Body }>(path, h) |
57
+ | fresh (deno.land/x/fresh, Fresh 1) | supported | yes: routes/<path>.ts: export const handler: Handlers = { GET, POST } |
58
+ | fresh (jsr:@fresh/core, Fresh 2) | supported | yes: routes/<path>.ts: export const handler = define.handlers({ GET, POST, ... })<br>yes: define.handlers((ctx) => ...): one function serves every method<br>yes: [...path] catch-all, [[optional]] parameter and (group) folder in Fresh routes |
59
+ | Gatsby Functions (gatsby) | supported | yes: src/api/<path>.ts default export handler(req, res): /api/<path>, every verb<br>yes: src/api/<dir>/index.ts: /api/<dir><br>yes: src/api/<dir>/[param].ts: one segment, value in req.params<br>yes: src/api/<dir>/[...].ts (or [...name].ts): matches everything after the splat |
60
+ | h3 | supported | yes: createRouter().get(path, defineEventHandler(...)) |
61
+ | hono | supported | yes: app.get/app.post(path, handler)<br>yes: app.route('/prefix', subApp)<br>yes: new Hono().basePath('/api').get(path) |
62
+ | honox | supported | yes: app/routes/<path>.ts: export default createRoute(...) serves GET<br>yes: app/routes/<path>.ts: export const POST = createRoute(...) and the other verbs |
63
+ | inversify-express-utils | not yet | no: @controller('/x') with @httpGet, @httpPost, @httpDelete |
64
+ | Netlify Edge Functions (@netlify/edge-functions) | supported | yes: netlify/edge-functions/<name>.ts default export with export const config = { path } |
65
+ | Netlify Functions (@netlify/functions) | supported | yes: netlify/functions/<name>.mts default export (req, context) with no config.path: /.netlify/functions/<name><br>yes: export const config: Config = { path: '/x/:id', method: [...] }: the path replaces /.netlify/functions/<name><br>yes: config.path as an array, with * wildcards and :named groups<br>yes: netlify/functions/<name>/index.mts or <name>/<name>.mts is function <name>; other files in the folder are helpers |
66
+ | Netlify Functions (@netlify/functions), Lambda-compatible | supported | yes: netlify/functions/<name>.ts exporting handler(event, context): served at /.netlify/functions/<name>, every verb |
67
+ | netlify.toml | supported | yes: [functions] directory = '<dir>': functions are read from that folder instead of netlify/functions<br>yes: [[redirects]] from = '/x/*' to = '/.netlify/functions/<name>' status = 200: a rewrite to a function<br>yes: [[edge_functions]] path = '/x/*' function = '<name>': routes an edge function from config |
68
+ | next | supported | yes: app/<path>/[[...slug]]/route.ts: optional catch-all segment, also matches the bare folder path<br>yes: app/<path>/[...slug]/route.ts: catch-all segment in the app router<br>yes: app/(group)/<path>/route.ts: a route group folder is left out of the URL<br>yes: app/<path>/_folder/...: a private folder opts out of routing; %5Ffolder is the escape that keeps a literal underscore<br>yes: a route.ts handler in a child segment of a page, outside app/api; page.tsx, layout.tsx and proxy.ts are not endpoints<br>yes: route.ts with `export const runtime = 'edge'`: an edge route handler<br>yes: a folder whose name holds a dot, app/rss.xml/route.ts, serves /rss.xml<br>yes: metadata route files app/sitemap.ts and app/robots.ts are special route handlers serving /sitemap.xml and /robots.txt<br>yes: src/app/<path>/route.ts: the app router placed under src/<br>yes: src/pages/api/<name>.ts: the pages router API placed under src/<br>yes: pages/api/<path>/[...slug].ts: catch-all API route, as NextAuth's pages/api/auth/[...nextauth].ts<br>yes: pages/api/<path>/[[...slug]].ts: optional catch-all API route, also matches the bare path<br>yes: app/ at the root and src/app both present: src/app is ignored |
69
+ | next (app router) | supported | yes: app/api/<path>/route.ts exporting GET/POST functions<br>yes: app/api/products/[id]/route.ts dynamic segment |
70
+ | next (pages router) | supported | yes: pages/api/<name>.ts typed NextApiRequest handler |
71
+ | nitropack (standalone Nitro) | supported | yes: a standalone Nitro app: routes/<path>.ts under srcDir (server/ here), verb from the file name<br>yes: a standalone Nitro app: api/<path>.ts served under /api |
72
+ | nuxt (nitro) | supported | yes: server/api/<name>.<verb>.ts default defineEventHandler: path and verb from the file name<br>yes: server/routes/<name>.ts: any verb, no /api prefix<br>yes: server/api/<path>/[id].get.ts: a dynamic segment with a verb in the file name; also two parameters in two folders<br>yes: server/api/<path>/index.post.ts: an index file with a verb serves the folder's own path<br>yes: server/api/<path>/[...slug].ts: a named catch-all, which also matches the base path<br>yes: server/api/<path>/[...].ts: an unnamed catch-all (params._) |
73
+ | RedwoodJS functions (@redwoodjs/api) | supported | yes: api/src/functions/<name>.ts exporting handler(event, context): served at /<name><br>yes: api/src/functions/<name>/<name>.ts or <name>/index.ts: function <name>; other files there are not exposed |
74
+ | routing-controllers | supported | yes: @JsonController('/x') with @Get('/'), @Get('/:id'), @Post('/') |
75
+ | tsoa | supported | yes: @Route('x') class extends Controller with @Get('{id}') and @Post() |
76
+ | type-graphql | supported | yes: @Resolver class with @Query/@Mutation, served by Apollo on /graphql |
77
+ | Vercel Functions (@vercel/node), no framework | supported | yes: api/<name>.ts default export handler(req: VercelRequest, res: VercelResponse): path from the file, every verb<br>yes: api/<name>.ts exporting GET, POST, ... with web Request and Response: the export name is the verb<br>yes: api/<name>.ts default export { fetch(request) }: one handler for every verb<br>yes: api/index.ts serves /api and api/<dir>/index.ts serves /api/<dir><br>yes: api/<dir>/[param].ts: a bracketed file name is one path segment, value in req.query<br>yes: api/<dir>/[...name].ts outside Next.js: still ONE segment, not a catch-all<br>yes: api/<name>.ts with export const config = { runtime: 'edge' }: same path rule as Node |
78
+ | vercel.json | supported | yes: rewrites: [{ source, destination: '/api/<fn>' }]: the source path is served by that function<br>yes: cleanUrls: true: a function is served without its extension; the extension form only redirects<br>yes: routes: [{ src: <regex>, methods, dest: '/api/<fn>' }]: regex source, optional verbs |
79
+
80
+ ## The language's own HTTP server
81
+
82
+ 0 supported, 0 partly, 3 not yet.
83
+
84
+ | Name | Status | Ways of using it that were tested |
85
+ |---|---|---|
86
+ | Bun.serve | not yet | no: Bun.serve({ routes: { '/path': { GET, POST } } }) in TS |
87
+ | Deno.serve | not yet | no: Deno.serve(opts, handler) with new URL(req.url).pathname comparison |
88
+ | Deno.serve + URLPattern | not yet | no: new URLPattern({ pathname: '/x/:id' }).exec(req.url) |
89
+
90
+ ## HTTP client libraries (the calls your code makes to other services)
91
+
92
+ 10 supported, 8 partly, 8 not yet.
93
+
94
+ | Name | Status | Ways of using it that were tested |
95
+ |---|---|---|
96
+ | @angular/common/http | partly | no: provideHttpClient(withInterceptors([...])) in app.config.ts (standalone bootstrap)<br>yes: class AuthInterceptor implements HttpInterceptor registered through HTTP_INTERCEPTORS (adds a header only) |
97
+ | @angular/common/http HttpClient | not yet | no: private http = inject(HttpClient) in a standalone component; this.http.put<T>(`${environment.apiUrl}/path`)<br>no: functional HttpInterceptorFn that puts environment.apiUrl in front of relative URLs; services call this.http.get<T>(relative)<br>no: NgModule app with HttpClientModule; service with constructor(private httpClient: HttpClient); get<T>(`${environment.apiUrl}/x/${id}`, { params })<br>no: constructor(private http: HttpClient); this.http.get<T>(`${environment.x}/path`)<br>no: this.http.post<T>(`${this.base}/path`, body) with the base in a class field<br>no: this.http.delete<T>(url with id) |
98
+ | @apollo/client | supported | yes: useQuery<T>(gql) and useMutation(gql) in a .tsx component, uri on the client in another module<br>yes: new ApolloClient({ uri }); client.query({ query: gql`...` }) |
99
+ | @builder.io/qwik-city | partly | no: export const useX = routeLoader$(async () => fetch(...))<br>yes: server$(async function (...) { fetch(...) }) |
100
+ | @nestjs/axios HttpService | partly | partly: this.httpService.get(url) wrapped in firstValueFrom<br>yes: this.httpService.axiosRef.post(url, body) |
101
+ | @reduxjs/toolkit (RTK Query) | not yet | no: createApi({ baseQuery: fetchBaseQuery({ baseUrl }), endpoints: b => ({ x: b.query({ query: () => 'path' }) }) }) |
102
+ | @tanstack/react-query | supported | yes: useQuery({ queryKey, queryFn }) where queryFn calls fetch<br>yes: useMutation({ mutationFn }) where mutationFn calls axios.post |
103
+ | @trpc/client | not yet | no: createTRPCProxyClient({ links: [httpBatchLink({ url })] }); trpc.ns.proc.query() |
104
+ | axios | partly | no: export const http: AxiosInstance = axios.create({ baseURL: import.meta.env.X ?? default }); http.get<T>(path) in another module<br>partly: const api: AxiosInstance = axios.create({ baseURL }); api.get<T>(path) |
105
+ | ky | supported | yes: ky.get(url).json<T>()<br>yes: ky.create({ prefixUrl }) then api.post(relative, { json }) |
106
+ | next | partly | partly: base URL from process.env.NEXT_PUBLIC_API_URL ?? default in a shared lib module |
107
+ | next (app router) | partly | partly: fetch(url, { next: { revalidate } }) in an async server component |
108
+ | next (middleware) | not yet | no: NextResponse.rewrite(new URL(path, backendOrigin)) in middleware.ts |
109
+ | next (next.config.js) | not yet | no: rewrites() returning { source, destination } that proxies to a backend<br>no: redirects() sending the browser to a backend URL |
110
+ | next (pages router) | supported | yes: export const getServerSideProps: GetServerSideProps = async () => fetch(...) |
111
+ | next (server actions) | partly | partly: 'use server' module exporting an async action that calls fetch |
112
+ | nuxt ($fetch in .vue) | supported | yes: $fetch(url, { method }) in a .vue function |
113
+ | nuxt (useFetch in .vue) | not yet | no: const { data } = await useFetch<T>(url) in <script setup lang="ts"> |
114
+ | nuxt (useFetch in a composable) | not yet | no: useFetch(path, { baseURL }) in composables/*.ts |
115
+ | ofetch | supported | yes: ofetch(url, { method: 'DELETE' }) |
116
+ | ofetch ($fetch, Nuxt) | supported | yes: $fetch(path, { baseURL }) |
117
+ | openapi-fetch | not yet | no: createClient<paths>({ baseUrl }).GET('/path/{id}', { params }) |
118
+ | react-router-dom | supported | yes: route object loader: async ({ params }: LoaderFunctionArgs) => fetch(...) |
119
+ | solid-js | supported | yes: createResource(fetcher) where the fetcher calls fetch |
120
+ | swr | partly | partly: useSWRMutation(url, fetcher with { arg }) in .tsx<br>yes: useSWR(url, fetcher): the key is the URL and the fetcher calls fetch(url) |
121
+ | vue (single-file component, lang ts) | supported | yes: axios.post in <script setup lang="ts"> |
122
+
123
+ ## The language's own HTTP client
124
+
125
+ 2 supported, 1 partly, 5 not yet.
126
+
127
+ | Name | Status | Ways of using it that were tested |
128
+ |---|---|---|
129
+ | astro (.astro page) | not yet | no: fetch in the --- frontmatter of a .astro page |
130
+ | EventSource (browser) | not yet | no: new EventSource(url) for server-sent events |
131
+ | fetch (browser) | partly | partly: fetch(`${API}/path`) in a function inside useEffect, base from import.meta.env ?? default |
132
+ | fetch (global) | not yet | no: async function http<T>(path, init) { fetch(`${BASE}${path}`, init) }; http<Fare>(`/fares/${id}`) |
133
+ | navigator.sendBeacon (browser) | not yet | no: navigator.sendBeacon(url, data): a POST |
134
+ | svelte (component, lang ts) | supported | yes: fetch in <script lang="ts"> of a .svelte file |
135
+ | WebSocket (browser) | supported | yes: new WebSocket('ws://host/path') |
136
+ | XMLHttpRequest (browser) | not yet | no: xhr.open('POST', url); xhr.send(body) |
137
+
138
+ ## gRPC
139
+
140
+ 3 supported, 1 partly, 1 not yet.
141
+
142
+ | Name | Status | Ways of using it that were tested |
143
+ |---|---|---|
144
+ | @connectrpc/connect (connect-es) | supported | yes: router.service(Desc, { method(req) {...} }) inside connectNodeAdapter({ routes })<br>yes: createClient(Desc, createConnectTransport({ baseUrl })); client.method(req) |
145
+ | @grpc/grpc-js + ts-proto | not yet | no: server.addService(XService, impl) where impl is a typed const, not an object literal in the call<br>no: new XClient(addr, creds) from makeGenericClientConstructor; client.method(req, cb) |
146
+ | @nestjs/microservices (gRPC transport) | supported | yes: nestjs-grpc-method: @GrpcMethod('Service', 'Method') with package and protoPath in createMicroservice options<br>yes: @GrpcStreamMethod('Service', 'Method') with Observable in and out<br>yes: @GrpcMethod('Service') with the RPC name taken from the method name, capitalised<br>yes: service_lookup: client.getService<T>('Service') then svc.method(req); package and url in ClientsModule.register |
147
+ | grpc-web | supported | yes: new {S}Client(url) from *_grpc_web_pb; client.method(req, metadata, cb)<br>yes: new {S}PromiseClient(url).method(req) |
148
+ | nice-grpc | partly | no: createServer().add(XDefinition, { method(req) {...} }) with ts-proto generic definitions<br>partly: createClient(XDefinition, createChannel(addr)); client.method(req) |
149
+
150
+ ## Databases and data stores
151
+
152
+ 5 supported, 3 partly, 7 not yet.
153
+
154
+ | Name | Status | Ways of using it that were tested |
155
+ |---|---|---|
156
+ | @libsql/client | supported | yes: client.execute(sql) and client.execute({ sql, args }) |
157
+ | @mikro-orm/core | not yet | no: @Entity({ tableName }); em.find(Entity, q); em.nativeDelete(Entity, q) |
158
+ | @neondatabase/serverless | not yet | no: const sql = neon(url); await sql`SELECT ...` |
159
+ | @nestjs/mongoose | not yet | no: @Schema({ collection }) class; @InjectModel(X.name) private model: Model<X>; this.model.find() |
160
+ | @nestjs/typeorm | not yet | no: constructor(@InjectRepository(Entity) private repo: Repository<Entity>); this.repo.find() |
161
+ | @prisma/client | partly | no: prisma.<model>.findMany/create where schema.prisma maps the model with @@map("table")<br>no: prisma.<model>.findUnique with no @@map: the table is the model name<br>yes: prisma.$queryRaw`SELECT ...` tagged template<br>no: main reads a table another service writes, through its own Prisma schema |
162
+ | @supabase/supabase-js | partly | yes: supabase.from('table').select()/update()<br>no: supabase.storage.from('bucket').upload()<br>yes: main reads a table another service writes |
163
+ | @typegoose/typegoose | not yet | no: @modelOptions({ schemaOptions: { collection } }); getModelForClass(C).find() |
164
+ | @upstash/redis | supported | yes: new Redis({ url, token }).incr/get(key) |
165
+ | @vercel/postgres | supported | yes: sql`SELECT ...` imported from @vercel/postgres |
166
+ | dexie | not yet | no: class X extends Dexie { t!: Table }; db.t.add(); db.t.where().equals().toArray() |
167
+ | drizzle-orm | partly | yes: pgTable('table', cols) in schema.ts; db.select().from(table) / insert(table) / delete(table)<br>no: db.execute(sql`...`) |
168
+ | kysely | supported | yes: db.selectFrom('t').selectAll().execute(); insertInto('t'); deleteFrom('t') |
169
+ | sequelize-typescript | not yet | no: @Table({ tableName }) class X extends Model; X.findAll()/create() |
170
+ | typeorm | supported | yes: @Entity('table') class; dataSource.getRepository(Entity).find()/save()<br>yes: createQueryBuilder().select().from(Entity, 'alias').getOne()<br>yes: dataSource.query(sql, params) |
171
+
172
+ ## Messaging and events (queues, topics, streams)
173
+
174
+ 4 supported, 1 partly, 4 not yet.
175
+
176
+ | Name | Status | Ways of using it that were tested |
177
+ |---|---|---|
178
+ | @azure/functions (v4) | supported | yes: app.serviceBusQueue('name', { queueName, handler }) |
179
+ | @nestjs/bullmq | not yet | no: @Processor('queue') class extends WorkerHost { process(job) }<br>no: @InjectQueue('queue') private q: Queue; this.q.add('job', data) |
180
+ | @nestjs/event-emitter | supported | yes: this.events.emit('name') and @OnEvent('name') handler |
181
+ | @nestjs/microservices | partly | yes: @MessagePattern('pattern') request-reply handler<br>yes: @EventPattern('pattern') event handler<br>no: @Inject('X') private client: ClientKafka; this.client.emit('pattern', data)<br>no: this.client.send('pattern', data) request-reply |
182
+ | @nestjs/websockets | supported | yes: @SubscribeMessage('event') on a @WebSocketGateway; this.server.emit('event') |
183
+ | AWS SAM template.yaml | not yet | no: Events: Type: SQS Properties: Queue, the handler consumes the queue |
184
+ | inngest | not yet | no: inngest.createFunction({ id }, { event: 'name' }, handler)<br>no: inngest.send({ name, data }) |
185
+ | mitt | supported | yes: const bus = mitt<Events>(); bus.on('x', h); bus.emit('x', data) |
186
+ | socket.io-client | not yet | no: io(url).emit('event') and socket.on('event') in a React component |
187
+
188
+ ## Build tools and package managers
189
+
190
+ 9 supported, 0 partly, 0 not yet.
191
+
192
+ | Name | Status | Ways of using it that were tested |
193
+ |---|---|---|
194
+ | Angular CLI workspace | supported | yes: angular.json with an application and a library under projects/, ng-package.json, tsconfig paths pointing the library at dist/ |
195
+ | Deno (deno.json imports) | supported | yes: deno.json "imports" map naming a local library folder and jsr:/npm: specifiers; tasks; no package.json |
196
+ | Deno 2 workspaces | supported | yes: root deno.json "workspace": ["./api", "./money"], each member with its own deno.json name and exports |
197
+ | Nx (integrated repo) | supported | yes: nx.json, project.json per project with projectType, no package.json in apps or libs, tsconfig.base.json paths for libs |
198
+ | Nx (package-based repo) | supported | yes: npm workspaces plus nx.json; each package has package.json and a project.json with projectType |
199
+ | tsconfig paths | supported | yes: one app with "baseUrl": "." and "paths": { "@app/*": ["src/*"], "@lib/*": ["lib/*"] }, imports through the aliases |
200
+ | Turborepo | supported | yes: turbo.json tasks, apps/web (Next.js) and apps/api (fastify), packages/ui, plus config-only packages (tsconfig, eslint-config) |
201
+ | TypeScript project references | supported | yes: root tsconfig.json "references" to packages/core and packages/server, composite true, outDir lib/ per package; no workspaces |
202
+ | Vite | supported | yes: vite.config.ts resolve.alias "@" to src and "~ui" to a sibling package folder, dist/ output, plus tsconfig paths for the same "@" |
203
+
204
+ ## Project and folder layouts
205
+
206
+ 3 supported, 0 partly, 0 not yet.
207
+
208
+ | Name | Status | Ways of using it that were tested |
209
+ |---|---|---|
210
+ | code generators (GraphQL codegen, Relay, OpenAPI, Prisma) | supported | yes: src/gen/ with a "generated, do not edit" header, __generated__/ next to components, prisma client in node_modules |
211
+ | Next.js (app router) | supported | yes: src/app/ with route.ts handlers and server components, src/lib helpers, "@/*" alias, .next/ output |
212
+ | Storybook | supported | yes: .storybook/ config, *.stories.tsx next to components, storybook-static/ build output |
213
+
214
+ ## Deployment files
215
+
216
+ 1 supported, 0 partly, 0 not yet.
217
+
218
+ | Name | Status | Ways of using it that were tested |
219
+ |---|---|---|
220
+ | docker-compose.yml at the monorepo root | supported | yes: compose services with build: ./apps/x and build: { context: ., dockerfile: apps/y/Dockerfile }, init ONCE at the repository root<br>yes: the same repository, but each app folder added to one project with its own `niro-local init` (how the coverage-run monorepos were measured) |
@@ -0,0 +1,99 @@
1
+ # Tokens, speed and accuracy
2
+
3
+ **This page explains how Niro measures tokens, speed and accuracy, and how you can measure them on your own work.** It does not give typical savings: they change from one task to the next and from one person to the next. Where a sentence is reasoning rather than measurement, it says so.
4
+
5
+ Words used on this page:
6
+ - a **token** is the unit a language model reads and is billed by, roughly half a word of code;
7
+ - a **session** is one conversation with a coding agent;
8
+ - **cache reads** are the tokens of the conversation so far, which the model re-reads, at a lower price, on every turn;
9
+ - **cache writes** are new tokens added to that stored conversation, billed once at a higher price;
10
+ - **MCP** (the Model Context Protocol) is the standard way a coding agent calls outside tools such as Niro's.
11
+
12
+ ## How tokens are counted
13
+
14
+ **Characters, divided by 2.09.** Niro counts the characters of every answer it returns, and turns them into tokens at 2.09 characters a token. That rate was measured against the model's own token counts, in Keycloak A/B transcripts, on results of more than 2,000 characters each. Over 69 Niro answers the median was 2.09 (middle half 1.83 to 2.25). Over 367 shell-tool results it was 2.10. Code and file paths pack more tokens into a character than English prose, for which 4 characters a token is the usual rule of thumb.
15
+ <!-- source: niro-local src/niro_local/mcp/receipt.py:27-33 (CHARS_PER_TOKEN = 2.09 and its measurement note); scratchpad niro-local-receipt-tokens/HOW-MEASURED-AND-VERDICT.md (table "Tool output | Results | Real characters per token, median": 69 niro-local answers 2.09, 1.83 to 2.25; 367 native 2.10) -->
16
+
17
+ The end-of-task line uses this rate on both of its sides, so the comparison between them does not depend on it.
18
+
19
+ ## How the by-hand figure is estimated
20
+
21
+ The end-of-task line and `niro impact` compare what Niro's answers cost with an estimate of finding the same things with text searches and file reads. The estimate follows how agents with only shell tools were seen to work: one search per name asked about, then about 40 lines read around each place an answer showed you, and one more search for what a name search cannot reach, such as the classes that run behind an interface. Only what you were shown is priced. The full method, with a worked example of the arithmetic, is in [evaluate-what-niro-did.md](evaluate-what-niro-did.md).
22
+ <!-- source: niro-local src/niro_local/mcp/by_hand.py:1-38 (the method; READ_LINES = 40, MAX_GREP_LINES = 250); docs/evaluate-what-niro-did.md section 1 -->
23
+
24
+ **How the estimate is checked.** It is compared with real A/B runs: what the shell-only session of a task really read, less what the Niro session of the same task still read with shell tools. In our A/B runs so far, added up over the tasks that printed a token figure, the two came close. For one task they can differ by several times either way, because the two sessions did not make the same searches. So read a single line's "instead of" as a rough order of size, not as a measurement.
25
+ <!-- source: scratchpad niro-cli-task-playbooks/AB-RESULTS.md "Receipt figures against measured tokens"; ab/receipt-adjusted.json (python3 -I ab/replay_receipts.py p1 p2) -->
26
+
27
+ ## How look-ups and round trips are counted
28
+
29
+ `niro impact` also counts, from the answers themselves, how many searches and file reads they stood for, and how many waits one after another an agent working by hand would have needed (a walk three levels deep needs at least three searches in a row). Both are counted from what each answer covered; neither is typed by the agent. How each is counted is in section 3 of [evaluate-what-niro-did.md](evaluate-what-niro-did.md).
30
+ <!-- source: niro-local src/niro_local/mcp/round_trips.py; src/niro_local/journal/store.py trips_avoided, levels_avoided -->
31
+
32
+ ## How the `answer` input was measured
33
+
34
+ The `answer` input asks for a short form of an answer, such as a count, the names, or the places only ([answer-shapes.md](answer-shapes.md)). To decide which forms to build, recorded Niro calls from real sessions were read together with what the agent did next. For each call, the smallest short form that still held what the agent then used was applied to the real answer text, and the tokens before and after were compared. The shapes were then run again on the release engine, on the calls whose code still existed, to check that estimate against real answers.
35
+ <!-- source: scratchpad niro-local-answer-shapes/page-data.json and BRIEF.md (the trial); niro-local docs/usecases/answer-shapes.md "What the replay showed (2026-11-10)" -->
36
+
37
+ **Why a short answer pays more than once.** This is reasoning, not a measurement. An answer stays in the conversation for every later turn, so a shorter answer is cheaper on every turn that follows it, not only on the turn that asked for it.
38
+
39
+ ## How we run an A/B, and how you can
40
+
41
+ **What an A/B is.** The same task is run twice: once with Niro and once without, with everything else the same. Ours are run like this:
42
+ - each task has an answer key, a list of facts read from the source by hand before any run;
43
+ - each session runs in headless Claude Code, on one pinned version with auto-update off, on the same model;
44
+ - each session starts with no project instructions and no memory, and with web tools off;
45
+ - the "without" arm has shell and file tools only; the "with" arm has the same tools plus Niro, over MCP or from the shell;
46
+ - in the "with" arm, `mark_task_complete` is blocked, so the end-of-task call a real session makes is not in its cost;
47
+ - in our latest runs, the "with" arm also had six lines of guidance asking it to use the `answer` input where it fits. niro-cli does not install those lines, and no run measured how much they helped;
48
+ - a blind reviewer marks each fact found or missed, with tool names removed from the answers, and counts anything invented;
49
+ - cost, wall time, tool calls, cache writes, cache reads and output tokens are read from each session's own result event.
50
+ <!-- source: scratchpad niro-local-tool-parity/ab-keycloak/bench.py and RESULTS-76-questions.md header; scratchpad niro-cli-task-playbooks/AB-PLAN.md and ab/bench.py (session_cmd blocks mcp__niro-local__mark_task_complete in the Niro arm; parse_stream); ab/answer-shapes-guide.md (the six lines) -->
51
+
52
+ **Why Niro over MCP can save less than Niro from the shell.** An MCP answer enters the conversation whole. A shell command's output can be cut with `grep` or `head` before it does. The `answer` input is how an agent asks for a shorter answer inside MCP.
53
+ <!-- source: scratchpad niro-local-receipt-tokens/HOW-MEASURED-AND-VERDICT.md ("it behaves like the native tools do: the agent runs it in the shell, can filter its output with grep or head, and only the final text enters the main conversation"); niro-local docs/usecases/answer-shapes.md "What it is" -->
54
+
55
+ **A run can show that the answer key is wrong.** In one of our tasks, both arms, in both runs, left out tests that the key named, and said why: at run time those tests never reach the method. Reading the code confirmed it, so the key was corrected and both arms were scored against the corrected key. When the two arms agree against your key, check the key against the code before you score.
56
+ <!-- source: scratchpad niro-cli-task-playbooks/AB-RESULTS.md "Key correction" (task p-03); keycloak model/storage/.../UserCredentialManager.java:310-316 -->
57
+
58
+ **Limits we keep in mind.** With one or two runs per task, one fact or a few cents on one task is within run-to-run noise, so compare totals over many tasks. Results from one agent version should not be compared with another version's. Our runs so far ask questions about code; none measured a full edit with its tests.
59
+ <!-- source: scratchpad niro-cli-task-playbooks/AB-PLAN.md "What is not measured"; niro-local-tool-parity/ab-keycloak/RESULTS-76-questions.md "Caveats" -->
60
+
61
+ To run your own, with the switch that turns Niro off for one Claude Code session and the numbers worth comparing, see section 4 of [evaluate-what-niro-did.md](evaluate-what-niro-did.md).
62
+
63
+ ## How accuracy pays off over a long session
64
+
65
+ **What is measured.** In our Keycloak A/B runs, cache reads were most of the tokens in both arms. So most of a session's tokens are the conversation being re-read on each turn, and anything that enters the conversation is paid for again on every later turn.
66
+ <!-- source: scratchpad niro-local-tool-parity/ab-keycloak/RESULTS-76-questions.md "Token detail" (cache reads against all tokens, both arms) -->
67
+
68
+ **What follows, as reasoning (not measured).** A missed caller costs more than the search that missed it:
69
+
70
+ 1. The agent lists callers with a text search and misses one that goes through an interface. In one of our Keycloak A/B sets, every fact the shell-only agent missed was of this kind.
71
+ 2. It edits the method and updates the callers it found. The missed caller now passes the wrong value.
72
+ 3. A test fails later in the session, or the bug ships. The agent goes back: it searches again, reads the files again, and reads the failing test's output. Each of those results enters the conversation.
73
+ 4. Every later turn re-reads all of that. A longer conversation also reaches the agent's context limit sooner, and the agent then compacts it (replaces the history with a summary), which drops detail it may need.
74
+ <!-- source: step 1: scratchpad niro-local-receipt-recheck/NATIVE-CHOICES.md ("Every one of the 21 c4 misses is 'Implementations of <Interface>.<method>'"; read by hand) -->
75
+
76
+ A list that says "complete" when it is, and "PARTIAL" with what was cut when it is not, avoids step 3. Niro's lists also have known gaps, and they are listed in [faq-wrong-or-missing-answers.md](faq-wrong-or-missing-answers.md); the playbooks show where to check with a text search.
77
+
78
+ ## Speed
79
+
80
+ | What | Measured | Where |
81
+ |---|---|---|
82
+ | A Niro call through MCP, warm | 0.04 to 0.07 s | the code-mode timing run, 10 calls each |
83
+ | The same call through `niro ask` (a new process each time) | about 0.45 s | same run |
84
+ | Full build of Keycloak (9,103 files) | 182 s | same run, machine load about 2 |
85
+ | Full build of Keycloak, for these docs | 3 min 17 s | the build log, 4 parse workers, load not recorded |
86
+ <!-- source: scratchpad niro-coverage-run/plans/code-mode.md ("A `niro ask` call takes 0.45 s against 0.04 to 0.07 s for the same call to a warm MCP server"); niro-coverage-run/code-mode-timing/README.md ("logs/init.log: the Keycloak build (9,103 files, 182 s)"); niro-coverage-run/plans/code-mode.md:70 ("load 1.9 to 2.1"); scratchpad niro-cli-task-playbooks/build-keycloak.log ("real 3m16.849s"; heavy-run.sh sets NIRO_LOCAL_PARSE_WORKERS=4) -->
87
+
88
+ A build's time depends on the size of your code, the number of cores and how busy the machine is.
89
+
90
+ ## What is not measured yet
91
+
92
+ - **Edits.** Every A/B so far asked questions about code; none measured a full change with its tests.
93
+ - **Many runs per question.** Our A/B runs ran each question once or twice per arm.
94
+ - **Helper agents.** No run in any Keycloak set started one, so the pattern where a helper does the reading and returns a summary is not measured either way.
95
+ <!-- source: scratchpad niro-local-receipt-tokens/HOW-MEASURED-AND-VERDICT.md "Helper agents" ("no run in either arm started a helper agent") and "How sure this is"; scratchpad niro-cli-task-playbooks/AB-PLAN.md "What is not measured" -->
96
+
97
+ ## Related
98
+
99
+ [evaluate-what-niro-did.md](evaluate-what-niro-did.md) (the end-of-task line, `niro impact`, and how to run your own A/B), [answer-shapes.md](answer-shapes.md), [lifecycle-overview.md](lifecycle-overview.md).
@@ -0,0 +1,42 @@
1
+ # Troubleshooting
2
+
3
+ **Start with three commands.** Most problems show up in one of them.
4
+
5
+ ```
6
+ niro status # this folder: indexed? built when? plan, sharing, agent, background updates
7
+ niro engine status # the engine: installed? where? does its database open?
8
+ niro plan # your plan and lines used
9
+ ```
10
+ <!-- source: src/commands/status.js:76-88; src/commands/engine.js:81-118; src/commands/plan.js:57-86 -->
11
+
12
+ **Then find your message.** [reference-messages.md](reference-messages.md) lists every message that has an answer, with a link to it.
13
+
14
+ ## By kind of problem
15
+
16
+ | Problem | Page |
17
+ |---|---|
18
+ | Installing, the engine, the background watcher | [faq-install-and-setup.md](faq-install-and-setup.md) |
19
+ | Signing in, your account | [faq-login-and-account.md](faq-login-and-account.md) |
20
+ | The line limit, the licence | [faq-licence-and-plans.md](faq-licence-and-plans.md) |
21
+ | Building the graph, files left out | [faq-indexing.md](faq-indexing.md) |
22
+ | An answer is wrong or missing something | [faq-wrong-or-missing-answers.md](faq-wrong-or-missing-answers.md) |
23
+ | Your coding agent does not see or use Niro | [faq-assistants-and-mcp.md](faq-assistants-and-mcp.md) |
24
+ | Branches, worktrees, temporary projects | [faq-worktrees-and-branches.md](faq-worktrees-and-branches.md) |
25
+ | What is sent, secrets, files Niro reads | [faq-privacy-and-security.md](faq-privacy-and-security.md) |
26
+ | Slow builds or answers, disk use | [faq-performance.md](faq-performance.md) |
27
+ | Upgrading | [faq-updates.md](faq-updates.md) |
28
+ | Removing Niro | [faq-uninstall.md](faq-uninstall.md) |
29
+ | Working without a network | [faq-offline.md](faq-offline.md) |
30
+ | The end-of-task line, feedback | [faq-feedback-and-receipts.md](faq-feedback-and-receipts.md) |
31
+ | Pull request review, root cause analysis | [hosted-only.md](hosted-only.md) |
32
+
33
+ ## Still stuck
34
+
35
+ Email **help@niro.ai** with:
36
+
37
+ - the command you ran;
38
+ - the message you saw;
39
+ - the output of `niro status` and `niro engine status`.
40
+
41
+ Remove anything private from them first.
42
+ <!-- source: help@niro.ai: Niraj, 2026-11-10 -->
@@ -0,0 +1,74 @@
1
+ # Uninstalling Niro Local
2
+
3
+ ## Remove one folder
4
+
5
+ To stop indexing one repo and free its lines, without removing Niro:
6
+
7
+ ```bash
8
+ niro plan remove ~/code/old-service
9
+ ```
10
+
11
+ Its graph is deleted; the folder and your code are not touched. Run `niro init` in it later to
12
+ index it again.
13
+ <!-- source: src/commands/plan.js:76-86 (niro plan remove); niro-local src/niro_local/cli.py:350 (remove: the folder's graph is removed and it is forgotten) -->
14
+
15
+ ## Remove Niro from this machine
16
+
17
+ ```bash
18
+ niro uninstall
19
+ ```
20
+
21
+ It lists what it will remove and asks first (`--yes` skips the question, for scripts):
22
+
23
+ 1. **Niro's entries in your AI assistants' settings**, for every assistant `niro mcp install`
24
+ configured: the `niro` MCP server, the Niro skills (`niro-skill`, `niro-local`), the hooks, and
25
+ the Niro section in files such as `~/.claude/CLAUDE.md`. Your own settings are left as they were.
26
+ 2. **The background watcher** (`niro-watch` in PM2). What happens to PM2's own start-at-login entry
27
+ depends on your computer. If PM2 still runs other programs, the entry is kept, because it is not
28
+ Niro's. On macOS, with nothing else in PM2, it is removed. Otherwise (on Linux), removing it needs
29
+ administrator rights, so the uninstall prints the command for you to run.
30
+ 3. **`~/.niro`**: your sign-in, settings and the local engine.
31
+ 4. **`~/.niro-local`**: the list of indexed folders and every graph (`graph.db`).
32
+ <!-- source: src/commands/uninstall.js:4-12 (the order of steps), :86-113 (lists, then asks; --yes), :141-160 (assistants, watcher, ~/.niro-local, ~/.niro), :150, :169-171 ("PM2 still starts on login (with nothing of Niro's). To remove that too: ..."); src/lib/service.js:301-328 (kept when PM2 runs other programs; the macOS LaunchAgents file removed; otherwise the `pm2 unstartup` command printed); src/mcp/teardown.js:1-27 (what an assistant's entry holds) -->
33
+
34
+ A step that fails is reported and the others still run. Then remove the command itself:
35
+
36
+ ```bash
37
+ npm uninstall -g @niroai/niro
38
+ ```
39
+
40
+ Restart your assistants so they stop loading the removed entries.
41
+ <!-- source: src/commands/uninstall.js:146-171 (a failed step sets the exit code and the rest still run; the two last steps printed) -->
42
+
43
+ ## Remove only the assistant connection
44
+
45
+ To keep the graphs but disconnect an assistant:
46
+
47
+ ```bash
48
+ niro mcp uninstall --client claude-code # one assistant
49
+ niro mcp uninstall --all # every assistant
50
+ ```
51
+ <!-- source: src/mcp/teardown.js:1-27, :645-653 -->
52
+
53
+ ## By hand, if `niro` will not run
54
+
55
+ 1. Assistant settings. For Claude Code: `claude mcp remove niro --scope user`, then delete
56
+ `~/.claude/skills/niro-skill` and `~/.claude/skills/niro-local`, and remove the block starting
57
+ with the Niro marker from `~/.claude/CLAUDE.md`. Other assistants: remove the `niro` server from
58
+ their MCP settings, and delete `~/.agents/skills/niro-skill` and `~/.agents/skills/niro-local`.
59
+ 2. The watcher: `pm2 delete niro-watch && pm2 save`.
60
+ 3. The data: `rm -rf ~/.niro ~/.niro-local`.
61
+ 4. The command: `npm uninstall -g @niroai/niro`.
62
+ <!-- source: src/mcp/setup.js:738-755, :1928-1985, :2393-2427 (where the entries and skills are written); src/commands/start.js:390-411 (the watcher is the PM2 process niro-watch); src/commands/uninstall.js:66-67 (~/.niro and ~/.niro-local) -->
63
+
64
+ ## Your account
65
+
66
+ <!-- source: src/commands/uninstall.js:94-101 ("Your Niro account stays"; code sharing is not turned off); src/lib/planUsage.js:116-119 (the sentence below) -->
67
+ Uninstalling removes Niro from this machine only; your Niro account stays. If you turned on code
68
+ sharing, turn it off under Settings, Privacy in the console first, so no machine you use can send
69
+ a whole repository any more. With sharing off, this still leaves a machine where Niro is
70
+ installed. Your code stays on this machine, except, for each line the audit flags, the whole method that
71
+ holds it and one line for each name it uses, sent with secrets masked and author and company names removed. A record of which
72
+ Niro tools your assistant used is also sent, to improve Niro and to show you its value. Niro's
73
+ tools answer your assistant from the graph on this machine and never send the contents of your
74
+ files.