@triangllabs/otis 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -0
- package/bin/otis +81 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Otis
|
|
2
|
+
|
|
3
|
+
Otis is an interactive terminal AI agent. The MVP runs locally as an OpenTUI CLI, uses a local TypeScript agent runtime, and calls an Otis-hosted Go gateway for inference and hosted web tools.
|
|
4
|
+
|
|
5
|
+
## MVP Architecture
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
OpenTUI CLI
|
|
9
|
+
-> local TypeScript agent runtime on Node 22+
|
|
10
|
+
-> Otis Go gateway on GCP Cloud Run
|
|
11
|
+
-> hosted model and web providers
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The local runtime owns the agent loop, local tool execution, session files, permissions, and UI events. The cloud gateway owns authentication, provider keys, system prompts, model routing, provider calls, hosted web tools, and future quota enforcement.
|
|
15
|
+
|
|
16
|
+
## Current Decisions
|
|
17
|
+
|
|
18
|
+
- Interactive CLI only for MVP.
|
|
19
|
+
- OpenTUI frontend.
|
|
20
|
+
- TypeScript local runtime on Node 22+.
|
|
21
|
+
- Go cloud gateway.
|
|
22
|
+
- GCP infrastructure managed with Terraform.
|
|
23
|
+
- GitHub Actions for CI/CD.
|
|
24
|
+
- Provider access behind Otis's backend keys.
|
|
25
|
+
- One configured primary model with fallback handling. No user-visible model picker.
|
|
26
|
+
- Local JSONL session files.
|
|
27
|
+
- Single-use invite code exchanged for a bearer token.
|
|
28
|
+
- Quota hooks implemented but disabled initially.
|
|
29
|
+
- Provider-native structured tool calls streamed through the gateway.
|
|
30
|
+
- Shell tool included.
|
|
31
|
+
|
|
32
|
+
## Initial User Flow
|
|
33
|
+
|
|
34
|
+
```txt
|
|
35
|
+
1. Invited user installs Otis.
|
|
36
|
+
2. User runs `otis`.
|
|
37
|
+
3. User selects Log in and enters a single-use invite code.
|
|
38
|
+
4. CLI stores a bearer token locally.
|
|
39
|
+
5. User starts chatting.
|
|
40
|
+
6. Otis streams responses through the Otis gateway.
|
|
41
|
+
7. Otis can read/search files, edit files, and run shell commands.
|
|
42
|
+
8. Session history is saved locally as JSONL.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Login
|
|
46
|
+
|
|
47
|
+
Run `otis` and press Enter on Log in, or run `otis login`, then enter a single-use invite code. Otis stores the issued bearer token locally under the user's Otis config directory.
|
|
48
|
+
|
|
49
|
+
The CLI defaults to the Otis dev gateway: `https://otis-gateway-dev-774436916534.us-central1.run.app`. Use `--gateway-url <gateway-url>` or `OTIS_GATEWAY_URL` to override it.
|
|
50
|
+
|
|
51
|
+
For development, a full `OTIS_GATEWAY_URL` and `OTIS_AUTH_TOKEN` pair still overrides saved auth.
|
|
52
|
+
|
|
53
|
+
## Repository Layout
|
|
54
|
+
|
|
55
|
+
```txt
|
|
56
|
+
apps/cli/ OpenTUI CLI app
|
|
57
|
+
packages/core/ Agent loop, events, context assembly
|
|
58
|
+
packages/gateway/ Gateway API client and protocol types
|
|
59
|
+
packages/tools/ Local tool implementations
|
|
60
|
+
packages/storage/ JSONL session storage
|
|
61
|
+
.github/workflows/ GitHub Actions workflows
|
|
62
|
+
docs/ Architecture and implementation notes
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Non-Goals For MVP
|
|
66
|
+
|
|
67
|
+
- Electron desktop app.
|
|
68
|
+
- Local inference.
|
|
69
|
+
- Print, JSON, or RPC modes.
|
|
70
|
+
- User-visible model selection.
|
|
71
|
+
- BYOK.
|
|
72
|
+
- OAuth.
|
|
73
|
+
- Cloud session sync.
|
|
74
|
+
- Extension marketplace.
|
|
75
|
+
- MCP.
|
|
76
|
+
- Subagents.
|
|
77
|
+
|
|
78
|
+
## Status
|
|
79
|
+
|
|
80
|
+
Interactive CLI runtime is in progress. Hosted gateway and GCP infrastructure live in separate private repos.
|
package/bin/otis
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process")
|
|
4
|
+
const fs = require("fs")
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const os = require("os")
|
|
7
|
+
|
|
8
|
+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"]
|
|
9
|
+
|
|
10
|
+
const platformMap = {
|
|
11
|
+
darwin: "darwin",
|
|
12
|
+
linux: "linux",
|
|
13
|
+
win32: "windows",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const archMap = {
|
|
17
|
+
x64: "x64",
|
|
18
|
+
arm64: "arm64",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const platform = platformMap[os.platform()] ?? os.platform()
|
|
22
|
+
const arch = archMap[os.arch()] ?? os.arch()
|
|
23
|
+
const binary = platform === "windows" ? "otis.exe" : "otis"
|
|
24
|
+
const packageName = `@triangllabs/otis-${platform}-${arch}`
|
|
25
|
+
|
|
26
|
+
function findBinary(startDir) {
|
|
27
|
+
let current = startDir
|
|
28
|
+
for (;;) {
|
|
29
|
+
const modules = path.join(current, "node_modules")
|
|
30
|
+
if (fs.existsSync(modules)) {
|
|
31
|
+
const candidate = path.join(modules, packageName, "bin", binary)
|
|
32
|
+
if (fs.existsSync(candidate)) return candidate
|
|
33
|
+
}
|
|
34
|
+
const parent = path.dirname(current)
|
|
35
|
+
if (parent === current) return
|
|
36
|
+
current = parent
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const scriptDir = path.dirname(fs.realpathSync(__filename))
|
|
41
|
+
const args = process.argv.slice(2)
|
|
42
|
+
|
|
43
|
+
if (args[0] === "update") {
|
|
44
|
+
console.log("Updating Otis...")
|
|
45
|
+
childProcess.execSync("npm install -g @triangllabs/otis@latest", { stdio: "inherit" })
|
|
46
|
+
console.log("Otis updated successfully.")
|
|
47
|
+
process.exit(0)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const resolved = process.env.OTIS_BIN_PATH || findBinary(scriptDir)
|
|
51
|
+
|
|
52
|
+
if (!resolved) {
|
|
53
|
+
console.error(
|
|
54
|
+
`Could not find the Otis binary for ${platform}-${arch}. ` +
|
|
55
|
+
`Try installing "${packageName}" manually, or set OTIS_BIN_PATH to the binary location.`,
|
|
56
|
+
)
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const child = childProcess.spawn(resolved, process.argv.slice(2), { stdio: "inherit" })
|
|
61
|
+
|
|
62
|
+
child.on("error", (error) => {
|
|
63
|
+
console.error(error.message)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
for (const signal of forwardedSignals) {
|
|
68
|
+
process.on(signal, () => {
|
|
69
|
+
try {
|
|
70
|
+
child.kill(signal)
|
|
71
|
+
} catch {}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
child.on("exit", (code, signal) => {
|
|
76
|
+
if (signal) {
|
|
77
|
+
process.kill(process.pid, signal)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
process.exit(typeof code === "number" ? code : 0)
|
|
81
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triangllabs/otis",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Interactive terminal AI agent by Triangl Labs",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"otis": "bin/otis"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "bun run apps/cli/src/index.ts",
|
|
12
|
+
"build": "bun run scripts/build.ts",
|
|
13
|
+
"format": "biome format --write .",
|
|
14
|
+
"lint": "biome lint .",
|
|
15
|
+
"check": "biome check .",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:coverage": "vitest run --coverage",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "bun run typecheck && bun run check && bun run test"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"bin/"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"ai",
|
|
26
|
+
"agent",
|
|
27
|
+
"cli",
|
|
28
|
+
"terminal",
|
|
29
|
+
"coding",
|
|
30
|
+
"assistant"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/triangllabs/otis.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/triangllabs/otis",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/triangllabs/otis/issues"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"optionalDependencies": {
|
|
47
|
+
"@triangllabs/otis-darwin-arm64": "0.1.7",
|
|
48
|
+
"@triangllabs/otis-darwin-x64": "0.1.7",
|
|
49
|
+
"@triangllabs/otis-linux-arm64": "0.1.7",
|
|
50
|
+
"@triangllabs/otis-linux-x64": "0.1.7"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@biomejs/biome": "^2.5.0",
|
|
54
|
+
"@opentui/core": "^0.4.1",
|
|
55
|
+
"@types/node": "^24.0.0",
|
|
56
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
57
|
+
"diff": "^9.0.0",
|
|
58
|
+
"typescript": "^5.8.0",
|
|
59
|
+
"vitest": "^4.1.9"
|
|
60
|
+
}
|
|
61
|
+
}
|