triss-coworker 0.13.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +546 -0
  3. package/bin/triss.js +263 -0
  4. package/docs/configuration.md +275 -0
  5. package/docs/extending.md +255 -0
  6. package/docs/integrations/confluence.md +65 -0
  7. package/docs/integrations/github.md +74 -0
  8. package/docs/integrations/gitlab.md +71 -0
  9. package/docs/integrations/jira.md +88 -0
  10. package/docs/integrations/linear.md +87 -0
  11. package/docs/mcp.md +267 -0
  12. package/docs/testing/test-plan-main.md +326 -0
  13. package/package.json +54 -0
  14. package/src/client.js +104 -0
  15. package/src/commands/ask.js +97 -0
  16. package/src/commands/chat.js +59 -0
  17. package/src/commands/commit-msg.js +91 -0
  18. package/src/commands/completion.js +179 -0
  19. package/src/commands/config.js +525 -0
  20. package/src/commands/extract.js +59 -0
  21. package/src/commands/fetch.js +35 -0
  22. package/src/commands/init.js +194 -0
  23. package/src/commands/mcp.js +135 -0
  24. package/src/commands/review.js +170 -0
  25. package/src/commands/status.js +75 -0
  26. package/src/commands/usage.js +77 -0
  27. package/src/commands/write.js +58 -0
  28. package/src/config.js +47 -0
  29. package/src/git.js +75 -0
  30. package/src/integrations/_atlassian.js +32 -0
  31. package/src/integrations/_contract.js +189 -0
  32. package/src/integrations/_registry.js +85 -0
  33. package/src/integrations/confluence/client.js +78 -0
  34. package/src/integrations/confluence/commands.js +94 -0
  35. package/src/integrations/confluence/index.js +71 -0
  36. package/src/integrations/github/client.js +112 -0
  37. package/src/integrations/github/commands.js +112 -0
  38. package/src/integrations/github/index.js +99 -0
  39. package/src/integrations/gitlab/client.js +97 -0
  40. package/src/integrations/gitlab/commands.js +119 -0
  41. package/src/integrations/gitlab/index.js +88 -0
  42. package/src/integrations/jira/adf.js +82 -0
  43. package/src/integrations/jira/client.js +97 -0
  44. package/src/integrations/jira/commands.js +163 -0
  45. package/src/integrations/jira/index.js +104 -0
  46. package/src/integrations/linear/client.js +166 -0
  47. package/src/integrations/linear/commands.js +133 -0
  48. package/src/integrations/linear/index.js +105 -0
  49. package/src/mcp/handlers.js +773 -0
  50. package/src/mcp/install.js +276 -0
  51. package/src/mcp/review-core.js +130 -0
  52. package/src/mcp/server.js +56 -0
  53. package/src/mcp/tools.js +665 -0
  54. package/src/models.js +20 -0
  55. package/src/net.js +89 -0
  56. package/src/paths.js +162 -0
  57. package/src/picker.js +137 -0
  58. package/src/safety.js +49 -0
  59. package/src/secrets.js +249 -0
  60. package/src/usage.js +163 -0
  61. package/src/web.js +172 -0
  62. package/templates/claude.md +151 -0
  63. package/templates/codex.md +51 -0
package/bin/triss.js ADDED
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+ const [nodeMajor] = process.versions.node.split('.').map(Number);
3
+ if (nodeMajor < 18) {
4
+ process.stderr.write(
5
+ `triss requires Node.js >= 18 (you are on ${process.versions.node}).\n` +
6
+ `Upgrade via nvm/fnm or https://nodejs.org/.\n`,
7
+ );
8
+ process.exit(1);
9
+ }
10
+
11
+ import { Command } from 'commander';
12
+ import pc from 'picocolors';
13
+ import { runAsk } from '../src/commands/ask.js';
14
+ import { runWrite } from '../src/commands/write.js';
15
+ import { runExtract } from '../src/commands/extract.js';
16
+ import { runFetch } from '../src/commands/fetch.js';
17
+ import { runReview } from '../src/commands/review.js';
18
+ import { runChat } from '../src/commands/chat.js';
19
+ import { runUsage } from '../src/commands/usage.js';
20
+ import {
21
+ runMcpServe,
22
+ runMcpInstall,
23
+ runMcpUninstall,
24
+ runMcpStatus,
25
+ } from '../src/commands/mcp.js';
26
+ import { runCommitMsg } from '../src/commands/commit-msg.js';
27
+ import { runInit } from '../src/commands/init.js';
28
+ import { runStatus } from '../src/commands/status.js';
29
+ import { runCompletion } from '../src/commands/completion.js';
30
+ import {
31
+ runWizard,
32
+ runSet,
33
+ runGet,
34
+ runList,
35
+ runPath,
36
+ runEdit,
37
+ runUnset,
38
+ } from '../src/commands/config.js';
39
+ import { loadIntegrations } from '../src/integrations/_registry.js';
40
+
41
+ const program = new Command();
42
+ program
43
+ .name('triss')
44
+ .description(
45
+ 'Cheap DeepSeek coworker for AI coding agents. Delegate bulk reads, ' +
46
+ 'boilerplate generation, chat extraction, and tracker I/O to save tokens.',
47
+ )
48
+ .version('0.12.1');
49
+
50
+ program
51
+ .command('init')
52
+ .description('Add the triss delegation block to your project (or globally)')
53
+ .option('-g, --global', 'install into the target agent global rules file instead of the current project')
54
+ .option('-t, --target <agent>', 'target agent (claude | codex | both); omit for an interactive prompt')
55
+ .option('-f, --force', 'force-replace an existing triss block without diffing')
56
+ .option('-s, --setup', 'after writing CLAUDE.md, run `triss config wizard` to fill in credentials')
57
+ .action(wrap(runInit));
58
+
59
+ program
60
+ .command('ask')
61
+ .description('Delegate bulk reading (files, URLs, and/or piped stdin) to DeepSeek; returns a structured summary')
62
+ .option('-p, --paths <paths...>', 'files or globs to read')
63
+ .option('-u, --urls <urls...>', 'http(s) URLs to fetch and convert to markdown')
64
+ .option('--stdin', 'read piped stdin as an additional source (for `cmd | triss ask --stdin ...`)')
65
+ .requiredOption('-q, --question <text>', 'question to answer about the corpus')
66
+ .option('-m, --model <name>', 'model preset (flash | pro) or full model id')
67
+ .option('--max-tokens <n>', 'token budget for reasoning + answer', (v) => parseInt(v, 10), 8192)
68
+ .option('--system <text>', 'override the system prompt')
69
+ .option('--no-stream', "disable streaming output (default streams when stdout is a TTY)")
70
+ .action(wrap(runAsk));
71
+
72
+ program
73
+ .command('write')
74
+ .description('Delegate boilerplate generation to DeepSeek')
75
+ .requiredOption('-s, --spec <text>', 'what to write')
76
+ .requiredOption('-t, --target <path>', 'output file path')
77
+ .option('-c, --context <path>', 'reference file to mimic in style')
78
+ .option('-m, --model <name>', 'model preset (flash | pro) or full model id')
79
+ .option('--max-tokens <n>', 'token budget for reasoning + output', (v) => parseInt(v, 10), 16384)
80
+ .action(wrap(runWrite));
81
+
82
+ program
83
+ .command('extract')
84
+ .description('Convert Claude Code JSONL transcripts into readable text')
85
+ .argument('<jsonl>', 'path to a .jsonl session file')
86
+ .option('-o, --output <path>', 'write to file instead of stdout')
87
+ .action((jsonl, opts) => wrap(runExtract)({ jsonl, ...opts }));
88
+
89
+ program
90
+ .command('fetch')
91
+ .description('Fetch URL(s) and return readable markdown; --question summarises via DeepSeek')
92
+ .argument('<urls...>', 'one or more http(s) URLs')
93
+ .option('-q, --question <text>', 'have DeepSeek summarise the fetched corpus')
94
+ .option('-m, --model <name>', 'model preset (flash | pro) or full model id')
95
+ .option('--max-tokens <n>', 'token budget for the summary')
96
+ .option('--timeout <ms>', 'per-request timeout in ms (default 30000)')
97
+ .option('--json', 'output JSON array of {url, markdown}')
98
+ .action((urls, opts) => wrap(runFetch)(urls, opts));
99
+
100
+ program
101
+ .command('chat [prompt]')
102
+ .description('Ask the worker model anything — no corpus, just a bare prompt.')
103
+ .option('--stdin', 'read prompt from piped stdin instead of [prompt] arg')
104
+ .option('-s, --system <text>', 'system prompt (e.g. role / persona)')
105
+ .option('-m, --model <name>', 'model preset (flash | pro) or full model id')
106
+ .option('--max-tokens <n>', 'token budget (default 4096)')
107
+ .option('--no-stream', 'disable streaming output')
108
+ .action((prompt, opts) => wrap(runChat)(prompt, opts));
109
+
110
+ program
111
+ .command('commit-msg')
112
+ .description('Generate a Git commit message from staged changes (Conventional Commits by default).')
113
+ .option('--apply', 'run `git commit -m <generated>` immediately instead of printing')
114
+ .option('--no-conventional', 'use a free-form commit subject instead of Conventional Commits')
115
+ .option('--type <type>', 'force the conventional type (feat | fix | refactor | …)')
116
+ .option('--scope <scope>', 'force the conventional scope')
117
+ .option('-m, --model <name>', 'model preset (flash | pro) or full id')
118
+ .option('--max-tokens <n>', 'token budget (default 2048)')
119
+ .action(wrap(runCommitMsg));
120
+
121
+ program
122
+ .command('review [pr]')
123
+ .description('Code review via DeepSeek. No arg: current branch vs default base. With <pr>: GitHub PR via gh.')
124
+ .option('-b, --base <branch>', 'compare against this branch (default: auto-detect origin/HEAD or main/master/develop)')
125
+ .option('--skip-issue', "don't try to look up a Jira/Linear ticket from the branch/PR title")
126
+ .option('-q, --question <text>', 'override the default review question')
127
+ .option('-m, --model <name>', 'model preset (flash | pro) or full model id (default: pro)')
128
+ .option('--max-tokens <n>', 'token budget for the review (default 8192)')
129
+ .option('--no-stream', 'disable streaming output')
130
+ .action((pr, opts) => wrap(runReview)(pr, opts));
131
+
132
+ program
133
+ .command('usage')
134
+ .description('Cumulative cost / token usage summary across all triss calls')
135
+ .option('--since <period>', 'period like 24h, 7d, 4w (default 24h)')
136
+ .option('--month', 'shortcut for --since 30d')
137
+ .option('--by-project', 'break down by working directory')
138
+ .option('--by-model', 'break down by model name')
139
+ .option('--by-label', 'break down by call source (ask, review, chat, …)')
140
+ .option('--json', 'dump raw log records as JSON')
141
+ .option('--reset', 'clear the usage log (cannot be undone)')
142
+ .action(wrap(runUsage));
143
+
144
+ program
145
+ .command('status')
146
+ .description('Show current configuration: API key, models, .env sources')
147
+ .action(wrap(runStatus));
148
+
149
+ program
150
+ .command('completion <shell>')
151
+ .description('Print shell completion script (bash | zsh). eval `triss completion bash`')
152
+ .action((shell) => wrap(runCompletion)(shell, program));
153
+
154
+ const config = program
155
+ .command('config')
156
+ .description('Manage credentials in ~/.config/triss/.env (global) or ./.triss.env (project)');
157
+
158
+ config
159
+ .command('wizard [target]')
160
+ .description('Interactive setup. Optional target: deepseek | jira | linear | …')
161
+ .option('-g, --global', 'save to ~/.config/triss/.env (default if not asked)')
162
+ .option('-l, --local', 'save to ./.triss.env (project-local override)')
163
+ .option('-f, --force', 're-prompt for keys that are already set')
164
+ .option('--standard', 'API key + one model only — skip the standard/advanced prompt')
165
+ .option('--advanced', 'full wizard with presets, base URL, integrations — skip the prompt')
166
+ .action(wrap(runWizard));
167
+
168
+ config
169
+ .command('set <KEY> [value]')
170
+ .description('Set a single variable. Without value, prompts (masked for *_KEY/_TOKEN). Use "-" to read from stdin.')
171
+ .option('-g, --global', 'save to global env file')
172
+ .option('-l, --local', 'save to project env file (./.triss.env)')
173
+ .action(wrap(runSet));
174
+
175
+ config
176
+ .command('get <KEY>')
177
+ .description('Print the active value (masked for secrets)')
178
+ .option('-g, --global', 'check only the global file')
179
+ .option('-l, --local', 'check only the project file')
180
+ .action(wrap(runGet));
181
+
182
+ config
183
+ .command('list')
184
+ .description('List variables from all (or one) env file')
185
+ .option('-g, --global', 'only the global file')
186
+ .option('-l, --local', 'only the project file')
187
+ .action(wrap(runList));
188
+
189
+ config
190
+ .command('path')
191
+ .description('Print path(s) of triss env file(s)')
192
+ .option('-g, --global', 'only the global path')
193
+ .option('-l, --local', 'only the project path')
194
+ .action(wrap(runPath));
195
+
196
+ config
197
+ .command('edit')
198
+ .description('Open the env file in $EDITOR ($VISUAL > $EDITOR > vi)')
199
+ .option('-g, --global', 'edit the global file')
200
+ .option('-l, --local', 'edit the project file')
201
+ .action(wrap(runEdit));
202
+
203
+ config
204
+ .command('unset <KEY>')
205
+ .description('Remove a variable from an env file')
206
+ .option('-g, --global', 'global file')
207
+ .option('-l, --local', 'project file')
208
+ .action(wrap(runUnset));
209
+
210
+ function wrap(fn) {
211
+ return async (...args) => {
212
+ try {
213
+ await fn(...args);
214
+ } catch (err) {
215
+ process.stderr.write(pc.red(`✗ ${err.message || err}\n`));
216
+ process.exit(1);
217
+ }
218
+ };
219
+ }
220
+
221
+ const mcp = program
222
+ .command('mcp')
223
+ .description('MCP server: register Triss as a tool provider for Claude Code (and Codex soon)');
224
+
225
+ mcp
226
+ .command('serve', { isDefault: true })
227
+ .description('Run the stdio MCP server. Claude Code starts this for you — no need to run it directly.')
228
+ .action(wrap(runMcpServe));
229
+
230
+ mcp
231
+ .command('install')
232
+ .description('Register triss as MCP server for Claude Code (~/.claude.json) or Codex (~/.codex/config.toml)')
233
+ .option('-g, --global', 'install into the agent global config (default)')
234
+ .option('-l, --local', 'install into <cwd>/.mcp.json (claude only)')
235
+ .option('-t, --target <agent>', 'target agent (claude | codex | both); omit for an interactive prompt')
236
+ .option('--command <cmd>', 'override the executable name (default: triss)')
237
+ .option('--args <args>', "override args, space-separated (default: 'mcp serve')")
238
+ .action(wrap(runMcpInstall));
239
+
240
+ mcp
241
+ .command('uninstall')
242
+ .description('Remove the triss MCP entry from the config')
243
+ .option('-g, --global', 'remove from the agent global config (default)')
244
+ .option('-l, --local', 'remove from <cwd>/.mcp.json (claude only)')
245
+ .option('-t, --target <agent>', 'target agent (claude | codex | both); omit for an interactive prompt')
246
+ .action(wrap(runMcpUninstall));
247
+
248
+ mcp
249
+ .command('status')
250
+ .description('Show whether triss is registered and with what command')
251
+ .option('-g, --global', '(default) check the agent global config')
252
+ .option('-l, --local', 'check <cwd>/.mcp.json (claude only)')
253
+ .option('-t, --target <agent>', 'target agent (claude | codex | both). Default: both')
254
+ .action(wrap(runMcpStatus));
255
+
256
+ // Plugin-style integrations (jira, linear, ...). See docs/extending.md.
257
+ const integrations = await loadIntegrations();
258
+ for (const manifest of integrations) {
259
+ const sub = program.command(manifest.name).description(manifest.description || manifest.name);
260
+ manifest.register(sub, { wrap });
261
+ }
262
+
263
+ await program.parseAsync(process.argv);
@@ -0,0 +1,275 @@
1
+ # Configuration
2
+
3
+ Triss reads its credentials from `.env` files. **You should never need to
4
+ create or edit those files by hand** — `triss config` handles everything.
5
+
6
+ ## TL;DR
7
+
8
+ ```bash
9
+ triss config wizard # interactive setup, prompts for every credential
10
+ triss status # verify what's loaded and from where
11
+ ```
12
+
13
+ That is the entire happy path.
14
+
15
+ ---
16
+
17
+ ## Where credentials live
18
+
19
+ Triss looks at two files, in this precedence order (highest first):
20
+
21
+ | Scope | Path | Purpose |
22
+ | --------- | --------------------------------- | --------------------------------------------------- |
23
+ | `process.env` | exported in your shell | Always wins. Use for CI / one-off overrides. |
24
+ | `local` | `<project>/.triss.env` | **Per-project** credentials. Overrides global here. |
25
+ | `global` | `~/.config/triss/.env` | Default for every project. |
26
+
27
+ **Same project, different Jira instances?** Put the org-wide DeepSeek key in
28
+ the global file, and the project-specific Jira `ATLASSIAN_*` triple in
29
+ `./.triss.env` for that repo. When you `cd` into another repo with its own
30
+ `.triss.env`, Triss automatically uses *that* repo's credentials. The
31
+ global Jira credentials (if any) keep working everywhere else.
32
+
33
+ `.triss.env` is auto-`chmod 600` and auto-added to that project's
34
+ `.gitignore` (if a `.gitignore` or `.git` exists). It is intentionally a
35
+ different filename from the more common `.env` so it never collides with
36
+ your application's own `.env`.
37
+
38
+ ---
39
+
40
+ ## `triss config` reference
41
+
42
+ ### `triss config wizard [target]`
43
+
44
+ Interactive prompt-driven setup.
45
+
46
+ When invoked **without a target**, the first question is **Standard vs
47
+ Advanced**:
48
+
49
+ | Mode | What it asks | Use when |
50
+ | --------- | ------------------------------------------------------------------------ | ----------------------------------------- |
51
+ | Standard | API key + one worker-model name (written to both `flash` & `pro`) | You just want it to work. Default. |
52
+ | Advanced | Every variable for every integration, with confirm-before-each provider | You need Jira/Linear, separate presets, custom base URL |
53
+
54
+ ```bash
55
+ triss config wizard # asks Standard / Advanced first
56
+ triss config wizard --standard # straight into Standard
57
+ triss config wizard --advanced # straight into Advanced
58
+ triss config wizard linear # targeted: only Linear, no mode prompt
59
+ triss config wizard linear --local # targeted, project-local
60
+ triss config wizard --advanced --force # re-prompt for already-set keys
61
+ ```
62
+
63
+ Behaviour:
64
+
65
+ - Asks "Global or Project?" up front (skip with `--global` / `--local`).
66
+ - Standard mode never asks about non-core integrations — run
67
+ `triss config wizard --advanced` (or a targeted invocation) later when
68
+ you actually need Jira/Linear.
69
+ - Advanced mode asks "Configure jira? (y/N)" before each non-core
70
+ provider so you only walk through the ones you use.
71
+ - Skips already-set values unless `--force`.
72
+ - Masks secret inputs (anything matching `*KEY*`, `*TOKEN*`, `*SECRET*`,
73
+ `*PASS*`).
74
+ - Uses every integration's `envVars` declaration — when you add a new
75
+ integration, the Advanced wizard picks up its variables for free.
76
+
77
+ ### `triss config set <KEY> [value]`
78
+
79
+ Set a single variable.
80
+
81
+ ```bash
82
+ triss config set DEEPSEEK_API_KEY # interactive masked prompt → global
83
+ triss config set ATLASSIAN_API_TOKEN --local # interactive → project
84
+ triss config set DEEPSEEK_FLASH_MODEL deepseek-chat # value as argument → global
85
+ echo "$KEY" | triss config set LINEAR_API_KEY - # read from stdin (CI-friendly)
86
+ ```
87
+
88
+ ### `triss config get <KEY>`
89
+
90
+ ```bash
91
+ triss config get DEEPSEEK_API_KEY # → "global sk-d…294" (masked)
92
+ triss config get DEEPSEEK_API_KEY --local
93
+ ```
94
+
95
+ Exits with code 1 if the key is missing.
96
+
97
+ ### `triss config list [--global|--local]`
98
+
99
+ Lists every variable in each env file, with masking on secrets.
100
+
101
+ ### `triss config path [--global|--local]`
102
+
103
+ Prints the absolute path(s) Triss will use, with `exists` / `missing`.
104
+
105
+ ### `triss config edit [--global|--local]`
106
+
107
+ Opens the env file in `$VISUAL` → `$EDITOR` → `vi`. Without a flag, asks
108
+ which file you mean.
109
+
110
+ ### `triss config unset <KEY> [--global|--local]`
111
+
112
+ Removes a variable.
113
+
114
+ ---
115
+
116
+ ## Known credentials
117
+
118
+ `triss config wizard` knows about:
119
+
120
+ | Target | Variables | Required |
121
+ | ---------- | ------------------------------------------------------------------------ | ----------------------------------------- |
122
+ | `deepseek` | `DEEPSEEK_API_KEY`, `DEEPSEEK_BASE_URL`, `DEEPSEEK_FLASH_MODEL`, `DEEPSEEK_PRO_MODEL` | only `DEEPSEEK_API_KEY` is required |
123
+ | `jira` | `ATLASSIAN_BASE_URL`, `ATLASSIAN_EMAIL`, `ATLASSIAN_API_TOKEN` | all three |
124
+ | `linear` | `LINEAR_API_KEY`, `LINEAR_API_URL` | only `LINEAR_API_KEY` is required |
125
+
126
+ When you add a new integration (see [extending.md](extending.md)), its
127
+ `envVars` declaration is automatically picked up — no wizard changes needed.
128
+
129
+ ---
130
+
131
+ ## Recipes — common setups end-to-end
132
+
133
+ ### A. Single shared workspace (most users)
134
+
135
+ One DeepSeek key, one Jira workspace, one Linear team. Everything global.
136
+
137
+ ```bash
138
+ # install (once)
139
+ curl -fsSL https://raw.githubusercontent.com/ayleen/triss-coworker/main/install.sh | bash
140
+
141
+ # wizard — Standard mode auto-installs MCP + global CLAUDE.md
142
+ triss config wizard --standard
143
+ # (later, when you want Jira/Linear too)
144
+ triss config wizard jira
145
+ triss config wizard linear
146
+
147
+ triss status # verify everything is "ready"
148
+ ```
149
+
150
+ Restart your Claude Code session. Done — every project sees the same
151
+ credentials.
152
+
153
+ ### B. Different Jira instances per project
154
+
155
+ You work for two clients with separate Atlassian Cloud orgs. Same
156
+ DeepSeek key everywhere, but `~/work-acme/` should hit Acme's Jira and
157
+ `~/work-beta/` should hit Beta's.
158
+
159
+ ```bash
160
+ # global once — DeepSeek key + MCP server + global CLAUDE.md fallback
161
+ triss config wizard --standard
162
+
163
+ # per-project — Acme
164
+ cd ~/work-acme
165
+ triss config wizard jira --local
166
+ # → ATLASSIAN_BASE_URL=https://acme.atlassian.net
167
+ # → ATLASSIAN_EMAIL=you@acme.com
168
+ # → ATLASSIAN_API_TOKEN=ATATT-acme...
169
+ # writes <pwd>/.triss.env, chmod 600, adds to .gitignore.
170
+
171
+ # per-project — Beta
172
+ cd ~/work-beta
173
+ triss config wizard jira --local
174
+ # → different Atlassian credentials, written to a separate .triss.env
175
+ ```
176
+
177
+ What happens when you open Claude Code:
178
+
179
+ | In `~/work-acme` | MCP server reads `~/work-acme/.triss.env` → `triss_jira_*` tools call Acme's Jira |
180
+ | In `~/work-beta` | MCP server reads `~/work-beta/.triss.env` → `triss_jira_*` tools call Beta's Jira |
181
+ | In any other dir | No project `.triss.env` → no Jira tools at all (just core) |
182
+
183
+ You don't reconfigure anything — the right credentials follow the
184
+ working directory automatically.
185
+
186
+ Verification: `triss status` from inside each project shows the
187
+ `[local]` source tag next to the ATLASSIAN_* rows.
188
+
189
+ ### C. Personal DeepSeek key per project
190
+
191
+ Want to charge a specific project to a separate billing account? Put
192
+ its `DEEPSEEK_API_KEY` in `<project>/.triss.env`. The global key in
193
+ `~/.config/triss/.env` keeps working everywhere else.
194
+
195
+ ```bash
196
+ cd ~/special-project
197
+ triss config set DEEPSEEK_API_KEY --local
198
+ # (masked prompt → ./.triss.env)
199
+ ```
200
+
201
+ ### D. CI / scripts
202
+
203
+ `process.env` always wins over `.env` files, so in CI you just export
204
+ what you need:
205
+
206
+ ```yaml
207
+ env:
208
+ DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
209
+ ATLASSIAN_BASE_URL: ${{ vars.ATLASSIAN_BASE_URL }}
210
+ ATLASSIAN_EMAIL: ${{ vars.ATLASSIAN_EMAIL }}
211
+ ATLASSIAN_API_TOKEN: ${{ secrets.ATLASSIAN_API_TOKEN }}
212
+ ```
213
+
214
+ `triss` commands then run unattended without needing any `.env` file.
215
+
216
+ ### E. Switching projects mid-session
217
+
218
+ You added `.triss.env` to a project after Claude Code was already
219
+ running there. As of v0.9.3, **no restart needed** — `.env` files are
220
+ re-read on every `tools/list` request, so the next time the agent or
221
+ `claude /mcp` lists tools, the new ones appear (or disappear).
222
+
223
+ The only thing that requires a restart is the *initial* MCP-server
224
+ registration — i.e. running `triss mcp install` for the first time.
225
+ After that, credential changes are live.
226
+
227
+ ## Troubleshooting
228
+
229
+ **"No DeepSeek API key found"** — `triss config wizard deepseek`.
230
+
231
+ **Wrong credentials picked up** — `triss status` shows the source for each
232
+ variable in `[global]` / `[local]` / `[env]` brackets. If you expected
233
+ project-local to win and it didn't, check that the file is named exactly
234
+ `.triss.env` (not `.env`) in your project root, and that you ran the
235
+ command from that directory.
236
+
237
+ **Switching organisations / Jira instances** — drop a `.triss.env` in the
238
+ project root with the project-specific keys; nothing else needs to change.
239
+
240
+ **Enabled an integration after `triss init`** — the agent's CLAUDE.md is
241
+ re-rendered on every `triss init`, including only integrations whose
242
+ required env vars are set. Run `triss init` again and your new
243
+ integration's delegation rules will appear in the file.
244
+
245
+ **Cost tracking** — every worker call appends to
246
+ `~/.cache/triss/usage.jsonl`. Inspect with `triss usage`. To opt out
247
+ entirely, set `TRISS_USAGE_LOG=0` before any call. To override the
248
+ baked-in DeepSeek list prices (e.g. you point Triss at a different
249
+ provider, or want the discounted rate), set
250
+ `TRISS_PRICE_<MODEL>=<input_cache_miss>,<input_cache_hit>,<output>` in
251
+ USD per token. Example: `TRISS_PRICE_DEEPSEEK_V4_PRO=4.35e-7,3.625e-9,8.7e-7`
252
+ applies the 75% promotional pricing to the pro preset.
253
+
254
+ **Claude Code integration step in the wizard** — split by mode:
255
+
256
+ | Mode in `triss config wizard` | What happens at the end |
257
+ | ----------------------------- | ----------------------- |
258
+ | **Standard** | Installs both paths automatically (MCP server + global CLAUDE.md). No question asked — Standard's job is to make sensible defaults work. |
259
+ | **Advanced** | Asks: 1) Both (default), 2) MCP only, 3) CLAUDE.md only, 4) Skip. |
260
+
261
+ The two paths cooperate: MCP is the primary tool surface; CLAUDE.md
262
+ acts as a fallback in case the MCP server can't load. Both can run
263
+ side-by-side without conflict.
264
+
265
+ Targeted invocations (`triss config wizard jira`) never touch the
266
+ integration paths — credential changes happen with no side effects.
267
+ Re-run the wizard to revisit; the install commands are idempotent.
268
+
269
+ When MCP is registered, `triss init` automatically prepends a hint to
270
+ the CLAUDE.md block that tells the agent to prefer the native MCP tools
271
+ over the Bash invocations. So you don't get duplicate routing logic in
272
+ the agent's prompt — just one path, with the other as fallback.
273
+
274
+ **CI** — set the variables as environment variables directly. `process.env`
275
+ wins over both files.