@tanagram/cli 0.4.13 → 0.4.18
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 +2 -0
- package/commands/run.go +21 -23
- package/dist/npm/darwin-arm64/LICENSE +21 -0
- package/dist/npm/darwin-arm64/README.md +267 -0
- package/dist/npm/darwin-arm64/tanagram +0 -0
- package/dist/npm/darwin-x64/LICENSE +21 -0
- package/dist/npm/darwin-x64/README.md +267 -0
- package/dist/npm/darwin-x64/tanagram +0 -0
- package/dist/npm/linux-arm64/LICENSE +21 -0
- package/dist/npm/linux-arm64/README.md +267 -0
- package/dist/npm/linux-arm64/tanagram +0 -0
- package/dist/npm/linux-x64/LICENSE +21 -0
- package/dist/npm/linux-x64/README.md +267 -0
- package/dist/npm/linux-x64/tanagram +0 -0
- package/dist/npm/tanagram_0.4.18_darwin_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_darwin_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_linux_amd64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_linux_arm64.tar.gz +0 -0
- package/dist/npm/tanagram_0.4.18_windows_amd64.zip +0 -0
- package/dist/npm/win32-x64/LICENSE +21 -0
- package/dist/npm/win32-x64/README.md +267 -0
- package/dist/npm/win32-x64/tanagram.exe +0 -0
- package/go.mod +2 -1
- package/go.sum +4 -0
- package/install.js +176 -22
- package/main.go +109 -20
- package/package.json +5 -4
- package/tui/welcome.go +1 -13
- package/utils/process.go +3 -2
- package/tui/puzzle.go +0 -694
- package/tui/renderer.go +0 -359
package/README.md
CHANGED
package/commands/run.go
CHANGED
|
@@ -3,6 +3,7 @@ package commands
|
|
|
3
3
|
import (
|
|
4
4
|
"context"
|
|
5
5
|
"fmt"
|
|
6
|
+
"log/slog"
|
|
6
7
|
"os"
|
|
7
8
|
"path/filepath"
|
|
8
9
|
"sync"
|
|
@@ -26,10 +27,10 @@ func spinner(stop chan bool, message string) {
|
|
|
26
27
|
for {
|
|
27
28
|
select {
|
|
28
29
|
case <-stop:
|
|
29
|
-
|
|
30
|
+
slog.Info("\r")
|
|
30
31
|
return
|
|
31
32
|
default:
|
|
32
|
-
|
|
33
|
+
slog.Info("spinner", "char", chars[i%len(chars)], "message", message)
|
|
33
34
|
i++
|
|
34
35
|
time.Sleep(100 * time.Millisecond)
|
|
35
36
|
}
|
|
@@ -80,7 +81,7 @@ func Run() error {
|
|
|
80
81
|
return err
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
slog.Info("Syncing policies with LLM", "files_to_process", len(filesToSync))
|
|
84
85
|
|
|
85
86
|
syncStart := time.Now()
|
|
86
87
|
ctx := context.Background()
|
|
@@ -102,19 +103,16 @@ func Run() error {
|
|
|
102
103
|
var completed int
|
|
103
104
|
var mu sync.Mutex
|
|
104
105
|
go func() {
|
|
105
|
-
chars := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
|
106
|
-
i := 0
|
|
107
106
|
for {
|
|
108
107
|
select {
|
|
109
108
|
case <-stop:
|
|
110
|
-
|
|
109
|
+
slog.Info("spinner stopped")
|
|
111
110
|
return
|
|
112
111
|
default:
|
|
113
112
|
mu.Lock()
|
|
114
113
|
c := completed
|
|
115
114
|
mu.Unlock()
|
|
116
|
-
|
|
117
|
-
i++
|
|
115
|
+
slog.Info("Processing files", "completed", c, "total", len(filesToSync))
|
|
118
116
|
time.Sleep(100 * time.Millisecond)
|
|
119
117
|
}
|
|
120
118
|
}
|
|
@@ -145,7 +143,7 @@ func Run() error {
|
|
|
145
143
|
close(stop)
|
|
146
144
|
time.Sleep(50 * time.Millisecond)
|
|
147
145
|
mu.Lock()
|
|
148
|
-
|
|
146
|
+
slog.Error("Failed to process file", "file", result.relPath)
|
|
149
147
|
mu.Unlock()
|
|
150
148
|
return fmt.Errorf("failed to extract policies from %s: %w", result.file, result.err)
|
|
151
149
|
}
|
|
@@ -162,7 +160,7 @@ func Run() error {
|
|
|
162
160
|
// Atomic update of counter and output (prevents race with spinner)
|
|
163
161
|
mu.Lock()
|
|
164
162
|
completed++
|
|
165
|
-
|
|
163
|
+
slog.Info("Processed file", "file", result.relPath, "policies", len(result.policies))
|
|
166
164
|
mu.Unlock()
|
|
167
165
|
}
|
|
168
166
|
|
|
@@ -176,7 +174,7 @@ func Run() error {
|
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
syncDuration := time.Since(syncStart)
|
|
179
|
-
|
|
177
|
+
slog.Info("Sync complete", "policies_synced", totalPolicies, "files_synced", len(filesToSync))
|
|
180
178
|
|
|
181
179
|
// Track sync metrics
|
|
182
180
|
metrics.Track("cli.sync.complete", map[string]interface{}{
|
|
@@ -202,10 +200,10 @@ func Run() error {
|
|
|
202
200
|
cloudPolicies, err = cloudStorage.LoadCloudPoliciesAsParserFormat(repoInfo.Owner, repoInfo.Name)
|
|
203
201
|
if err != nil {
|
|
204
202
|
// Cloud policies exist but failed to load - warn but continue
|
|
205
|
-
|
|
203
|
+
slog.Warn("Failed to load cloud policies", "error", err)
|
|
206
204
|
cloudPolicies = []parser.Policy{}
|
|
207
205
|
} else if len(cloudPolicies) > 0 {
|
|
208
|
-
|
|
206
|
+
slog.Info("Loaded cloud policies", "count", len(cloudPolicies), "owner", repoInfo.Owner, "repo", repoInfo.Name)
|
|
209
207
|
}
|
|
210
208
|
}
|
|
211
209
|
// If repo detection failed, silently continue with local-only policies
|
|
@@ -214,7 +212,7 @@ func Run() error {
|
|
|
214
212
|
policies := storage.MergePolicies(localPolicies, cloudPolicies)
|
|
215
213
|
|
|
216
214
|
if len(policies) == 0 {
|
|
217
|
-
|
|
215
|
+
slog.Info("No enforceable policies found")
|
|
218
216
|
return nil
|
|
219
217
|
}
|
|
220
218
|
|
|
@@ -223,9 +221,9 @@ func Run() error {
|
|
|
223
221
|
totalMerged := len(policies)
|
|
224
222
|
|
|
225
223
|
if totalCloud > 0 {
|
|
226
|
-
|
|
224
|
+
slog.Info("Total policies", "total", totalMerged, "local", totalLocal, "cloud", totalCloud, "after_merge", totalMerged)
|
|
227
225
|
} else {
|
|
228
|
-
|
|
226
|
+
slog.Info("Loaded local policies", "count", totalLocal)
|
|
229
227
|
}
|
|
230
228
|
|
|
231
229
|
// Check if a snapshot exists (from PreToolUse hook)
|
|
@@ -233,7 +231,7 @@ func Run() error {
|
|
|
233
231
|
useSnapshot := false
|
|
234
232
|
|
|
235
233
|
if snapshot.Exists(gitRoot) {
|
|
236
|
-
|
|
234
|
+
slog.Info("Snapshot detected - checking only Claude's changes")
|
|
237
235
|
|
|
238
236
|
// Load snapshot
|
|
239
237
|
snap, err := snapshot.Load(gitRoot)
|
|
@@ -265,13 +263,13 @@ func Run() error {
|
|
|
265
263
|
|
|
266
264
|
// Delete snapshot after using it
|
|
267
265
|
if err := snapshot.Delete(gitRoot); err != nil {
|
|
268
|
-
|
|
266
|
+
slog.Warn("Failed to delete snapshot", "error", err)
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
useSnapshot = true
|
|
272
270
|
} else {
|
|
273
271
|
// No snapshot - fall back to checking all git changes
|
|
274
|
-
|
|
272
|
+
slog.Info("Checking all changes (unstaged + staged)")
|
|
275
273
|
diffResult, err := gitpkg.GetAllChanges()
|
|
276
274
|
if err != nil {
|
|
277
275
|
return fmt.Errorf("error getting git diff: %w", err)
|
|
@@ -282,14 +280,14 @@ func Run() error {
|
|
|
282
280
|
|
|
283
281
|
if len(changesToCheck) == 0 {
|
|
284
282
|
if useSnapshot {
|
|
285
|
-
fmt.
|
|
283
|
+
fmt.Fprint(os.Stdout, "No changes detected since snapshot\n")
|
|
286
284
|
} else {
|
|
287
|
-
fmt.
|
|
285
|
+
fmt.Fprint(os.Stdout, "No changes to check\n")
|
|
288
286
|
}
|
|
289
287
|
return nil
|
|
290
288
|
}
|
|
291
289
|
|
|
292
|
-
|
|
290
|
+
slog.Info("Scanning changed lines", "count", len(changesToCheck))
|
|
293
291
|
|
|
294
292
|
// Get API key once upfront before checking
|
|
295
293
|
apiKey, err := config.GetAPIKey()
|
|
@@ -337,6 +335,6 @@ func Run() error {
|
|
|
337
335
|
}
|
|
338
336
|
|
|
339
337
|
// No violations found - output success message
|
|
340
|
-
fmt.
|
|
338
|
+
fmt.Fprint(os.Stdout, "No policy violations found")
|
|
341
339
|
return nil
|
|
342
340
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tanagram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Tanagram CLI
|
|
2
|
+
|
|
3
|
+
A lightweight Go CLI that enforces policies from `AGENTS.md` files on your local git changes.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Run `tanagram` before committing to catch policy violations locally:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
$ tanagram
|
|
11
|
+
|
|
12
|
+
✗ Found 1 policy violation(s):
|
|
13
|
+
|
|
14
|
+
webui/src/Button.tsx:42 - [No hardcoded colors] Don't use hard-coded color values; use theme colors instead
|
|
15
|
+
> background: "#FF5733"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
### Quick Start (3 steps)
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 1. Install globally via npm
|
|
24
|
+
npm install -g @tanagram/cli
|
|
25
|
+
|
|
26
|
+
# 2. Automatically add a Claude Code hook
|
|
27
|
+
tanagram config claude
|
|
28
|
+
# and/or if you use Cursor:
|
|
29
|
+
tanagram config cursor
|
|
30
|
+
|
|
31
|
+
# 3. Run tanagram (will prompt for API key on first run)
|
|
32
|
+
tanagram
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Requirements:**
|
|
36
|
+
- Node.js >= 14.0.0
|
|
37
|
+
- **Anthropic API Key** (you'll be prompted to enter it on first run)
|
|
38
|
+
|
|
39
|
+
The CLI is written in Go but distributed via npm for easier installation and version management. During installation, npm automatically downloads the Go compiler and builds the binary for your platform (no manual setup needed!).
|
|
40
|
+
|
|
41
|
+
### API Key Setup
|
|
42
|
+
|
|
43
|
+
Tanagram uses Claude AI (via Anthropic API) to extract policies from your instruction files. On first run, you'll be prompted to enter your API key, which will be saved to `~/.tanagram/config.json`.
|
|
44
|
+
|
|
45
|
+
**Get an API key:**
|
|
46
|
+
1. Sign up at [https://console.anthropic.com](https://console.anthropic.com)
|
|
47
|
+
2. Create an API key in the dashboard
|
|
48
|
+
3. Run `tanagram` and enter your key when prompted
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Check all changes (unstaged + staged) - automatically syncs if policies changed
|
|
54
|
+
tanagram
|
|
55
|
+
# or explicitly:
|
|
56
|
+
tanagram run
|
|
57
|
+
|
|
58
|
+
# Manually sync instruction files to cache
|
|
59
|
+
tanagram sync
|
|
60
|
+
|
|
61
|
+
# View all cached policies
|
|
62
|
+
tanagram list
|
|
63
|
+
|
|
64
|
+
# Show help
|
|
65
|
+
tanagram help
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Smart Caching:** Policies are cached and automatically resynced when instruction files change (detected via MD5 hash).
|
|
69
|
+
|
|
70
|
+
### Commands
|
|
71
|
+
|
|
72
|
+
- **`run`** (default) - Check git changes against policies with auto-sync
|
|
73
|
+
- **`sync`** - Manually sync all instruction files to cache
|
|
74
|
+
- **`list`** - View all cached policies (shows enforceable vs unenforceable)
|
|
75
|
+
- **`help`** - Show usage information
|
|
76
|
+
|
|
77
|
+
### Claude Code Hook
|
|
78
|
+
|
|
79
|
+
Install the CLI as a Claude Code [hook](https://code.claude.com/docs/en/hooks) to have Claude automatically iterate on Tanagram's output.
|
|
80
|
+
|
|
81
|
+
**Easy setup (recommended):**
|
|
82
|
+
```bash
|
|
83
|
+
tanagram config claude
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This automatically adds the hook to your `~/.claude/settings.json`. It's safe to run multiple times and will preserve any existing settings.
|
|
87
|
+
|
|
88
|
+
**Check hook status:**
|
|
89
|
+
```bash
|
|
90
|
+
tanagram config list
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Manual setup (alternative):**
|
|
94
|
+
If you prefer to manually edit your settings, add this to your `~/.claude/settings.json` (user settings) or `.claude/settings.json` (project settings):
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"hooks": {
|
|
99
|
+
"SessionStart": [
|
|
100
|
+
{
|
|
101
|
+
"hooks": [
|
|
102
|
+
{
|
|
103
|
+
"command": "tanagram snapshot",
|
|
104
|
+
"type": "command"
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"matcher": "startup|clear"
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
"Stop": [
|
|
111
|
+
{
|
|
112
|
+
"hooks": [
|
|
113
|
+
{
|
|
114
|
+
"command": "tanagram",
|
|
115
|
+
"type": "command"
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If you have existing hooks, you can merge this hook into your existing config.
|
|
125
|
+
|
|
126
|
+
### Cursor Hook
|
|
127
|
+
|
|
128
|
+
Install the CLI as a Cursor Code [hook](https://cursor.com/docs/agent/hooks) to have Cursor automatically iterate on Tanagram's output.
|
|
129
|
+
|
|
130
|
+
**Easy setup (recommended):**
|
|
131
|
+
```bash
|
|
132
|
+
tanagram config cursor
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This automatically adds the hook to your `~/.cursor/hooks.json`. It's safe to run multiple times and will preserve any existing settings.
|
|
136
|
+
|
|
137
|
+
**Check hook status:**
|
|
138
|
+
```bash
|
|
139
|
+
tanagram config list
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Manual setup (alternative):**
|
|
143
|
+
If you prefer to manually edit your settings, add this to your `~/.cursor/hooks.json` (user settings) or `.cursor/hooks.json` (project settings):
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"hooks": {
|
|
148
|
+
"beforeSubmitPrompt": [
|
|
149
|
+
{
|
|
150
|
+
"command": "tanagram snapshot"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"stop": [
|
|
154
|
+
{
|
|
155
|
+
"command": "tanagram"
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
},
|
|
159
|
+
"version": 1
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If you have existing hooks, you can merge this hook into your existing config.
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## How It Works
|
|
167
|
+
|
|
168
|
+
1. **Finds instruction files** - Searches for `AGENTS.md`, `POLICIES.md`, `CLAUDE.md`, `BUGBOT.md`, and `.cursor/rules/*.mdc` in your git repository
|
|
169
|
+
2. **Checks cache** - Loads cached policies and MD5 hashes from `.tanagram/`
|
|
170
|
+
3. **Auto-syncs** - Detects file changes via MD5 and automatically resyncs if needed
|
|
171
|
+
4. **LLM extraction** - Uses Claude AI to extract ALL policies from instruction files
|
|
172
|
+
5. **Gets git diff** - Analyzes all your changes (unstaged + staged)
|
|
173
|
+
6. **LLM detection** - Checks violations using intelligent semantic analysis
|
|
174
|
+
7. **Reports results** - Terminal output with detailed reasoning for each violation
|
|
175
|
+
|
|
176
|
+
### Cache Location
|
|
177
|
+
|
|
178
|
+
Policies are cached in `.tanagram/cache.gob` at your git repository root. Add this to your `.gitignore`:
|
|
179
|
+
|
|
180
|
+
```gitignore
|
|
181
|
+
.tanagram/
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### What Can Be Enforced
|
|
185
|
+
|
|
186
|
+
**Everything!** Because the LLM reads and understands code like a human:
|
|
187
|
+
|
|
188
|
+
**Simple patterns:**
|
|
189
|
+
- "Don't use hard-coded colors" → Detects `#FF5733`, `rgb()`, etc.
|
|
190
|
+
- "Use ruff format, not black" → Detects `black` usage
|
|
191
|
+
- "Always use === instead of ==" → Detects `==` operators
|
|
192
|
+
|
|
193
|
+
**Complex guidelines:**
|
|
194
|
+
- "Break down code into modular functions" → Analyzes function length and complexity
|
|
195
|
+
- "Don't deeply layer code" → Detects excessive nesting
|
|
196
|
+
- "Ensure no code smells" → Identifies common anti-patterns
|
|
197
|
+
- "Use structured logging with request IDs" → Checks logging patterns
|
|
198
|
+
- "Prefer async/await for I/O" → Understands async patterns
|
|
199
|
+
|
|
200
|
+
**Language-specific idioms:**
|
|
201
|
+
- Knows Go uses PascalCase for exports (not Python's snake_case)
|
|
202
|
+
- Won't flag Go code for missing Python type hints
|
|
203
|
+
- Understands JavaScript !== Python !== Go
|
|
204
|
+
|
|
205
|
+
### Exit Codes
|
|
206
|
+
|
|
207
|
+
- `0` - No violations found
|
|
208
|
+
- `2` - Violations found (triggers Claude Code automatic fix behavior)
|
|
209
|
+
|
|
210
|
+
## Example
|
|
211
|
+
|
|
212
|
+
Create an `AGENTS.md` in your repo with policies:
|
|
213
|
+
|
|
214
|
+
```markdown
|
|
215
|
+
# Development Policies
|
|
216
|
+
|
|
217
|
+
- Don't use hard-coded color values; use theme colors instead
|
|
218
|
+
- Use ruff format for Python formatting, not black
|
|
219
|
+
- Always use async/await for database operations
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Or use Cursor rules files in `.cursor/rules/`:
|
|
223
|
+
|
|
224
|
+
```markdown
|
|
225
|
+
---
|
|
226
|
+
description: TypeScript coding standards
|
|
227
|
+
globs: ["*.ts", "*.tsx"]
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
# TypeScript Standards
|
|
231
|
+
|
|
232
|
+
- Use strict type checking
|
|
233
|
+
- Avoid using 'any' type
|
|
234
|
+
- Prefer interfaces over type aliases
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Then run `tanagram` to enforce them locally!
|
|
238
|
+
|
|
239
|
+
**Note:** For `.mdc` files, Tanagram extracts policies from the markdown content only (YAML frontmatter is used by Cursor and ignored during policy extraction).
|
|
240
|
+
|
|
241
|
+
## Tanagram Web Integration
|
|
242
|
+
|
|
243
|
+
You can also use [Tanagram](https://tanagram.ai) to manage policies across your organization and enforce them on PRs.
|
|
244
|
+
If you have policies defined online, you can enforce them while you develop locally with the CLI as well.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Connect your account
|
|
248
|
+
tanagram login
|
|
249
|
+
|
|
250
|
+
# Download policies from your Tanagram account and cache them locally
|
|
251
|
+
tanagram sync
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
For customers with an on-prem installation, set the `TANAGRAM_WEB_HOSTNAME` environment variable to the URL of your Tanagram instance — for example:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
export TANAGRAM_WEB_HOSTNAME=https://yourcompany.tanagram.ai
|
|
258
|
+
|
|
259
|
+
tanagram login
|
|
260
|
+
tanagram sync
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
Built by [@fluttermatt](https://x.com/fluttermatt) and the [Tanagram](https://tanagram.ai/) team. Talk to us [on Twitter](https://x.com/tanagram_) or email: founders AT tanagram.ai
|
|
266
|
+
|
|
267
|
+
#
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tanagram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|