callkaro-cli-unofficial 0.1.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.
- package/README.md +196 -0
- package/dist/commands/accounts.js +275 -0
- package/dist/commands/agentPhone.js +50 -0
- package/dist/commands/agentVersions.js +228 -0
- package/dist/commands/agents.js +455 -0
- package/dist/commands/analytics.js +77 -0
- package/dist/commands/analyzers.js +130 -0
- package/dist/commands/auditStrategies.js +178 -0
- package/dist/commands/batches.js +189 -0
- package/dist/commands/calls.js +193 -0
- package/dist/commands/chatAgents.js +251 -0
- package/dist/commands/jsonInput.js +56 -0
- package/dist/commands/kpi.js +681 -0
- package/dist/commands/models.js +99 -0
- package/dist/commands/numbers.js +154 -0
- package/dist/commands/ongoing.js +39 -0
- package/dist/commands/secrets.js +124 -0
- package/dist/commands/setJson.js +42 -0
- package/dist/commands/simulations.js +139 -0
- package/dist/commands/skills.js +318 -0
- package/dist/commands/transcribers.js +95 -0
- package/dist/commands/update.js +86 -0
- package/dist/commands/voices.js +211 -0
- package/dist/data/catalog.json +1978 -0
- package/dist/endpoints.json +8 -0
- package/dist/index.js +684 -0
- package/dist/lib/agentImport.js +60 -0
- package/dist/lib/agentUpdate.js +262 -0
- package/dist/lib/api.js +287 -0
- package/dist/lib/browserAuth.js +93 -0
- package/dist/lib/catalog.js +136 -0
- package/dist/lib/config.js +354 -0
- package/dist/lib/dashboardApi.js +197 -0
- package/dist/lib/dashboardAuth.js +339 -0
- package/dist/lib/dbFields.js +30 -0
- package/dist/lib/logQuery.js +140 -0
- package/dist/lib/looseJson.js +198 -0
- package/dist/lib/session.js +17 -0
- package/dist/lib/types.js +15 -0
- package/dist/lib/ui.js +91 -0
- package/dist/lib/versions.js +19 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# cku
|
|
2
|
+
|
|
3
|
+
An unofficial fork of the [CallKaro CLI](https://www.npmjs.com/package/@callkaro-official/cli). It installs as `cku`, so it sits alongside the official `ck` without either one overwriting the other.
|
|
4
|
+
|
|
5
|
+
Manage voice agents, calls, phone numbers, batch campaigns, simulations and analytics from the terminal.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
cku agents list
|
|
9
|
+
cku calls make --agent <id> --to <number> --test
|
|
10
|
+
cku accounts use tejas@acme.com
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why this exists
|
|
16
|
+
|
|
17
|
+
Three reasons, all of them things the official CLI can't do.
|
|
18
|
+
|
|
19
|
+
**It doesn't fight the official CLI for a name.** Both packages write to the same global `node_modules/.bin`. If both were called `ck`, whichever npm install ran last would silently take over. You'd type `ck`, get official behaviour, and get no signal anything had changed. `cku` cannot collide.
|
|
20
|
+
|
|
21
|
+
**It holds several accounts at once.** The official CLI keeps one session. This one keeps a file per account, and can pin an account to a folder, so two agents working in two directories can't switch each other out from under themselves.
|
|
22
|
+
|
|
23
|
+
**It fixes three upstream bugs.** Details and evidence in [`docs/BUG-REPORT.md`](docs/BUG-REPORT.md).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install --global callkaro-cli-unofficial
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or, to work on it directly:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/TejasLamba2006/callkaro-cli-unofficial
|
|
37
|
+
cd callkaro-cli-unofficial
|
|
38
|
+
npm install && npm run build && npm link
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Node 18 or newer.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## How it fits together
|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
Every arrow above is a real import or a real call, checked against the source. `api.ts` is the only thing that touches the network. `config.ts` is the only thing that reads a token, which is why every route to the backend goes through it. The ASCII version below says the same thing for the terminal:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
┌─ cku ─────────────────────────────────────────────┐
|
|
53
|
+
│ index.ts commander wiring, 44 subcommands │
|
|
54
|
+
│ ↓ │
|
|
55
|
+
│ commands/ agents, calls, numbers, batches, │
|
|
56
|
+
│ sim, analytics, audits, skills │
|
|
57
|
+
│ ↓ │
|
|
58
|
+
│ lib/ api.ts, config.ts, agentUpdate.ts, │
|
|
59
|
+
│ looseJson.ts, ui.ts, catalog.ts │
|
|
60
|
+
└──────┬────────────────────────────────────────────┘
|
|
61
|
+
↓
|
|
62
|
+
account resolution, highest precedence first
|
|
63
|
+
│
|
|
64
|
+
CK_TOKEN → CK_ACCOUNT → ./.cku.json → global → none
|
|
65
|
+
│
|
|
66
|
+
↓
|
|
67
|
+
backend.callkaro.ai
|
|
68
|
+
/cli/* · 61 endpoints · Bearer ck_5b02…
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
One thing worth knowing up front: the official CLI ships **compiled output only**. Its npm package has `dist/` and nothing else: no TypeScript, no tests, no build config, and it isn't in any public repo. So this is a reconstruction. The `/cli/*` contract was rebuilt from the published bundle, and the command surface matches v4.0.0 exactly (44 subcommands against the official 40; the four extras are the `accounts` group).
|
|
72
|
+
|
|
73
|
+
The public API at `api.callkaro.ai` is a different thing entirely, with an `X-API-KEY` header and three documented endpoints. This CLI doesn't use it. [`docs/architecture.md`](docs/architecture.md) covers the boundaries, and [`docs/api.md`](docs/api.md) has the full endpoint inventory.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Several accounts
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cku login # add an account
|
|
81
|
+
cku accounts # list them, * marks the active one
|
|
82
|
+
cku accounts use <email> # change the global default
|
|
83
|
+
cku accounts remove <email> # forget one
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Pin an account to a folder
|
|
87
|
+
|
|
88
|
+
If you're running more than one agent at a time, this is the part that matters.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
cd ~/work/bot-alpha && cku accounts bind alpha@acme.com
|
|
92
|
+
cd ~/work/bot-beta && cku accounts bind beta@other.com
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`bind` writes a `.cku.json` into that directory containing just the account slug and no token, so it's safe to commit. Every `cku` command in that folder (and anything nested under it) uses that account, regardless of what the global default says.
|
|
96
|
+
|
|
97
|
+
Why this matters: `cku accounts use` changes **shared** state. Two agents in two folders both calling it would overwrite each other, and whichever wrote last would silently redirect the other bot's commands to the wrong CallKaro account. No error, just wrong data. A bound folder never reads shared state at all, so that can't happen.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
cku accounts status
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
tells you which account is in effect *and where the choice came from*: env var, this folder, or the global default. Worth running first when something looks off, since acting on the wrong account is the most damaging mistake available here.
|
|
104
|
+
|
|
105
|
+
Override for a single command with `CK_ACCOUNT=<email> cku whoami`.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Fixes over the official CLI
|
|
110
|
+
|
|
111
|
+
Full write-ups, with line numbers and reproduction steps, in [`docs/BUG-REPORT.md`](docs/BUG-REPORT.md).
|
|
112
|
+
|
|
113
|
+
**`agents update --set` silently deleted agent configuration.** Send a partial `voice_configuration` and the backend replaces the whole object, so nine keys, including the voice itself, were dropped, and the command reported success. The agent kept working; it just couldn't make calls. The fix already exists upstream as `deepMerge`, with a comment describing exactly this problem. Nothing ever called it.
|
|
114
|
+
|
|
115
|
+
**`skills install` failed on every run on Windows.** `tar` was handed an absolute path, and `C:/Users/...` gets read as a `<host>:<path>` remote spec, so tar tried to connect to a machine named "C". Since the temp path always sits under a drive root, this never worked there. It's also the only way to install the skill pack, so an agent couldn't bootstrap itself on Windows. Linux and macOS were never affected; no drive letters, no colon in a path.
|
|
116
|
+
|
|
117
|
+
**`agents export` can't always be re-imported.** For some agents the export response omits a field the CLI's own validator requires, so the round trip fails. That part needs a backend fix; a workaround is in the `callkaro-cli` skill.
|
|
118
|
+
|
|
119
|
+
My own first port of this fix had a worse version of the same bug: I merged against the agent's default version, so updating any other version could swap its voice for the default's. That's fixed and covered by a test, and the details are in [`docs/official-fixes.md`](docs/official-fixes.md).
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Staying current
|
|
124
|
+
|
|
125
|
+
A daily workflow checks npm for a newer `@callkaro-official/cli` and, when it finds one, regenerates the provider catalogue and opens a PR onto the `official` branch. It never merges anything itself.
|
|
126
|
+
|
|
127
|
+
| Branch | What's on it |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `official` | Clean base. Changes only arrive through sync PRs. |
|
|
130
|
+
| `main` | `official` plus your customizations. Your working branch. |
|
|
131
|
+
|
|
132
|
+
To take an update: review the PR, merge into `official`, then merge `official` into `main`.
|
|
133
|
+
|
|
134
|
+
Worth knowing what the sync does and doesn't do. It regenerates `src/data/catalog.json` — the provider tables and the per-slot LLM model lists — because those change when the dashboard changes. It does **not** port command code, because the official source isn't available, so that's maintained by hand. The PR lists which commands were added or removed so you know what to look at.
|
|
135
|
+
|
|
136
|
+
Keep your customizations in as few files as you can. That diff is the one you have to resolve on every update.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm run build # tsc -> dist/, copy endpoints.json, chmod +x
|
|
144
|
+
npm test # build, then node --test
|
|
145
|
+
npm run test:only # tests without rebuilding
|
|
146
|
+
npm run dev # build and run
|
|
147
|
+
npm run catalog:sync # regenerate the catalogue: provider tables + per-slot LLM lists, from the server
|
|
148
|
+
npm run catalog:check # fail if catalog.json disagrees with the server snapshot (offline)
|
|
149
|
+
npm run catalog:check:live # same, against the live server (needs `cku dashboard login`)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Single test file:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
node --test tests/agentUpdate.test.js
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
210 tests. No linter or formatter is configured.
|
|
159
|
+
|
|
160
|
+
### Layout
|
|
161
|
+
|
|
162
|
+
`src/index.ts` is commander wiring and almost nothing else. Every command, flag and description is declared there. `src/commands/` holds one module per command group. `src/lib/` holds the shared layer, and that's where the rules worth knowing live:
|
|
163
|
+
|
|
164
|
+
- **`api.ts`** is the only place that talks to the backend. Adding an endpoint means adding a method here, not a raw `fetch` in a command.
|
|
165
|
+
- **`looseJson.ts`** has two parsers, chosen by where the text came from. Values from argv get a repair pass, because PowerShell strips quotes out of single-quoted arguments and `--set '{"name":"x"}'` arrives as `{name:x}`. Values from files or stdin are parsed strictly, since the shell never touched them and a real typo should be reported rather than guessed at.
|
|
166
|
+
- **`agentUpdate.ts`** merges updates into objects the backend replaces wholesale, so a partial update doesn't drop its siblings.
|
|
167
|
+
- **`ui.ts`** sets `process.exitCode` rather than calling `process.exit()`. On Windows, exiting while a fetch socket is still closing trips a libuv assertion that aborts with code 127 and a pile of C-level noise.
|
|
168
|
+
|
|
169
|
+
### Regenerating docs
|
|
170
|
+
|
|
171
|
+
Both of these read from the code, so they can't drift:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
node scripts/gen-api-inventory.js # endpoint tables in docs/api.md
|
|
175
|
+
node scripts/gen-cli-reference.js ../callkaro-skills/skills/callkaro-cli/REFERENCE.md
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Skills
|
|
181
|
+
|
|
182
|
+
The CLI installs a skill pack for Claude Code and other coding agents, so an agent can drive `cku` without being told how.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
cku skills install --claude # this fork's pack
|
|
186
|
+
cku skills install --claude --official # the upstream pack
|
|
187
|
+
cku skills update # refresh every install
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Targets include `--cursor --copilot --gemini --windsurf --roo --opencode --cline --codex --all`, plus `--project` to install into a repo rather than your user account. Skills ship from GitHub, so a new one reaches users without a CLI release.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
Apache-2.0, matching the upstream CLI.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.projectAccount = exports.keyValue = void 0;
|
|
7
|
+
exports.loginCommand = loginCommand;
|
|
8
|
+
exports.registerCommand = registerCommand;
|
|
9
|
+
exports.logoutCommand = logoutCommand;
|
|
10
|
+
exports.whoamiCommand = whoamiCommand;
|
|
11
|
+
exports.configCommand = configCommand;
|
|
12
|
+
exports.accountsCommand = accountsCommand;
|
|
13
|
+
exports.accountsBindCommand = accountsBindCommand;
|
|
14
|
+
exports.accountsUnbindCommand = accountsUnbindCommand;
|
|
15
|
+
exports.accountsStatusCommand = accountsStatusCommand;
|
|
16
|
+
exports.accountsUseCommand = accountsUseCommand;
|
|
17
|
+
exports.accountsRemoveCommand = accountsRemoveCommand;
|
|
18
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
19
|
+
const path_1 = require("path");
|
|
20
|
+
const config_1 = require("../lib/config");
|
|
21
|
+
Object.defineProperty(exports, "projectAccount", { enumerable: true, get: function () { return config_1.projectAccount; } });
|
|
22
|
+
const browserAuth_1 = require("../lib/browserAuth");
|
|
23
|
+
const dashboardAuth_1 = require("../lib/dashboardAuth");
|
|
24
|
+
const ui_1 = require("../lib/ui");
|
|
25
|
+
Object.defineProperty(exports, "keyValue", { enumerable: true, get: function () { return ui_1.keyValue; } });
|
|
26
|
+
/** `ck login` — add or replace an account, then make it active. */
|
|
27
|
+
async function loginCommand(opts) {
|
|
28
|
+
// Default: sign in through the browser so no password is typed in a terminal.
|
|
29
|
+
if (!opts.password) {
|
|
30
|
+
// browserAuth saves the session and prints the one success line.
|
|
31
|
+
if (!(await (0, browserAuth_1.browserAuth)('login')))
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const { api } = (0, config_1.getClient)();
|
|
37
|
+
const answers = await (0, prompts_1.default)([
|
|
38
|
+
{ type: opts.email ? null : 'text', name: 'email', message: 'Email' },
|
|
39
|
+
{ type: 'password', name: 'password', message: 'Password' },
|
|
40
|
+
], { onCancel: () => process.exit(1) });
|
|
41
|
+
const email = opts.email || answers.email;
|
|
42
|
+
const password = answers.password;
|
|
43
|
+
if (!email || !password)
|
|
44
|
+
return (0, ui_1.handleError)(new Error('Email and password are required'));
|
|
45
|
+
const res = await api.login(email, password);
|
|
46
|
+
(0, ui_1.success)((0, config_1.saveLogin)(res.user?.email || email, { token: res.token, user: res.user }));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
(0, ui_1.handleError)(err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function registerCommand(opts) {
|
|
53
|
+
try {
|
|
54
|
+
if (opts.google) {
|
|
55
|
+
const user = await (0, browserAuth_1.browserAuth)('signup');
|
|
56
|
+
if (!user)
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { api } = (0, config_1.getClient)();
|
|
61
|
+
const answers = await (0, prompts_1.default)([
|
|
62
|
+
{ type: opts.email ? null : 'text', name: 'email', message: 'Email' },
|
|
63
|
+
{ type: opts.name ? null : 'text', name: 'name', message: 'Your name' },
|
|
64
|
+
{ type: 'password', name: 'password', message: 'Password' },
|
|
65
|
+
], { onCancel: () => process.exit(1) });
|
|
66
|
+
const email = opts.email || answers.email;
|
|
67
|
+
const name = opts.name || answers.name;
|
|
68
|
+
if (!email || !name || !answers.password) {
|
|
69
|
+
return (0, ui_1.handleError)(new Error('Email, name and password are required'));
|
|
70
|
+
}
|
|
71
|
+
await api.registerStart(email);
|
|
72
|
+
(0, ui_1.info)(`We sent a 6-digit code to ${email}.`);
|
|
73
|
+
const { otp } = await (0, prompts_1.default)([{ type: 'text', name: 'otp', message: 'Verification code' }], { onCancel: () => process.exit(1) });
|
|
74
|
+
if (!otp)
|
|
75
|
+
return (0, ui_1.handleError)(new Error('Verification code is required'));
|
|
76
|
+
const res = await api.registerVerify({ email, name, password: answers.password, otp });
|
|
77
|
+
(0, ui_1.info)('Account created.');
|
|
78
|
+
(0, ui_1.success)((0, config_1.saveLogin)(res.user?.email || email, { token: res.token, user: res.user }));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
(0, ui_1.handleError)(err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function logoutCommand() {
|
|
85
|
+
let revoked = false;
|
|
86
|
+
try {
|
|
87
|
+
const { api, cfg } = (0, config_1.getClient)();
|
|
88
|
+
if (cfg.token)
|
|
89
|
+
await api.logout();
|
|
90
|
+
revoked = Boolean(cfg.token);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
// Revocation is best-effort: clear locally even if the server is unreachable.
|
|
94
|
+
(0, ui_1.info)(err instanceof Error ? `Server revoke failed (${err.message}); clearing the local session anyway.` : 'Server revoke failed; clearing the local session anyway.');
|
|
95
|
+
}
|
|
96
|
+
if (process.env.CK_TOKEN) {
|
|
97
|
+
// clearSession leaves stored accounts alone under CK_TOKEN.
|
|
98
|
+
if (revoked)
|
|
99
|
+
(0, ui_1.success)('Revoked the CK_TOKEN session. Stored accounts are untouched.');
|
|
100
|
+
else
|
|
101
|
+
(0, ui_1.info)('The CK_TOKEN session was not revoked. Stored accounts are untouched.');
|
|
102
|
+
(0, ui_1.info)(' Unset CK_TOKEN to fall back to your stored accounts.');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const email = (0, config_1.clearSession)();
|
|
106
|
+
if (!email)
|
|
107
|
+
return void (0, ui_1.info)('Not signed in — nothing to log out of.');
|
|
108
|
+
(0, ui_1.success)(`Logged out of ${email}. Run \`cku login\` to sign back in.`);
|
|
109
|
+
}
|
|
110
|
+
async function whoamiCommand(opts) {
|
|
111
|
+
try {
|
|
112
|
+
const { api } = (0, config_1.getClient)();
|
|
113
|
+
const res = await api.me();
|
|
114
|
+
if (opts.json)
|
|
115
|
+
return void console.log(JSON.stringify(res, null, 2));
|
|
116
|
+
const user = res.user || res;
|
|
117
|
+
if (!user?.email) {
|
|
118
|
+
(0, ui_1.fail)('Could not read your account details. Run `cku login`.');
|
|
119
|
+
process.exitCode = 1;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
(0, ui_1.success)(`Logged in as ${user.email}`);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
(0, ui_1.handleError)(err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function configCommand() {
|
|
129
|
+
const cfg = (0, config_1.loadConfig)();
|
|
130
|
+
const envToken = process.env.CK_TOKEN;
|
|
131
|
+
const accounts = (0, config_1.listAccounts)();
|
|
132
|
+
(0, ui_1.info)('Effective configuration:');
|
|
133
|
+
(0, ui_1.info)('');
|
|
134
|
+
(0, ui_1.info)(` backend ${(0, config_1.backendUrl)()}`);
|
|
135
|
+
(0, ui_1.info)(` web ${(0, config_1.webUrl)()}`);
|
|
136
|
+
(0, ui_1.info)(` skills repo ${(0, config_1.skillsRepo)()}`);
|
|
137
|
+
(0, ui_1.info)(` config dir ${config_1.configPath.replace(/config\.json$/, '')}`);
|
|
138
|
+
if (envToken) {
|
|
139
|
+
(0, ui_1.info)(' session CK_TOKEN from the environment (ignores stored accounts)');
|
|
140
|
+
}
|
|
141
|
+
else if (cfg.token) {
|
|
142
|
+
const src = (0, config_1.accountSource)();
|
|
143
|
+
const where = src === 'env' ? 'CK_ACCOUNT' : src === 'project' ? 'this folder' : 'global default';
|
|
144
|
+
(0, ui_1.info)(` account ${cfg.email || 'unknown'} (from ${where})`);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
(0, ui_1.info)(' session not signed in — run `cku login`');
|
|
148
|
+
}
|
|
149
|
+
(0, ui_1.info)(` accounts ${accounts.length} saved${accounts.length ? ` — \`cku accounts list\`` : ''}`);
|
|
150
|
+
}
|
|
151
|
+
/** `cku accounts` — list / switch / add / remove stored logins. */
|
|
152
|
+
async function accountsCommand(opts) {
|
|
153
|
+
const accounts = (0, config_1.listAccounts)();
|
|
154
|
+
const active = (0, config_1.activeAccount)();
|
|
155
|
+
// Never print tokens: this output ends up in agent transcripts and CI logs.
|
|
156
|
+
if (opts.json) {
|
|
157
|
+
return void console.log(JSON.stringify(accounts.map(({ token, ...a }) => ({ ...a, signedIn: Boolean(token) })), null, 2));
|
|
158
|
+
}
|
|
159
|
+
if (!accounts.length) {
|
|
160
|
+
return void (0, ui_1.info)('No accounts saved. Run `cku login` to add one.');
|
|
161
|
+
}
|
|
162
|
+
(0, ui_1.table)(['', 'EMAIL', 'CK TOKEN', 'DASHBOARD', 'SAVED'], accounts.map((a) => [
|
|
163
|
+
active && (0, config_1.slugFor)(a.email || '') === active ? '*' : '',
|
|
164
|
+
a.email || '(unknown)',
|
|
165
|
+
a.token ? 'yes' : 'no',
|
|
166
|
+
a.dashboard?.token ? (0, dashboardAuth_1.expiryLabel)(a.dashboard.token) : '—',
|
|
167
|
+
a.savedAt ? new Date(a.savedAt).toLocaleString('en-US') : '',
|
|
168
|
+
]));
|
|
169
|
+
const source = (0, config_1.accountSource)();
|
|
170
|
+
(0, ui_1.info)(`\n* = active account (${source === 'env' ? 'CK_ACCOUNT' : source === 'project' ? 'this folder\'s .cku.json' : 'global default'}).`);
|
|
171
|
+
(0, ui_1.info)('CK TOKEN gates /cli/* commands. DASHBOARD gates /auth/* and /v2/*, and is a separate');
|
|
172
|
+
(0, ui_1.info)('login that expires in 24h — add one with `cku dashboard login`.');
|
|
173
|
+
if (source === 'global') {
|
|
174
|
+
(0, ui_1.info)(' Tip: `cku accounts bind <email>` pins an account to this folder, so parallel');
|
|
175
|
+
(0, ui_1.info)(' agents in different folders cannot switch each other out from under themselves.');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Selecting a signed-out account succeeds, but commands will fail until it signs in. */
|
|
179
|
+
function warnIfSignedOut(a) {
|
|
180
|
+
if (!a.token)
|
|
181
|
+
(0, ui_1.info)(` ${a.email} is signed out — run \`cku login\` to sign it back in.`);
|
|
182
|
+
}
|
|
183
|
+
/** `cku accounts bind <email>` — pin an account to this folder (`.cku.json`). */
|
|
184
|
+
async function accountsBindCommand(emailOrSlug, opts) {
|
|
185
|
+
const accounts = (0, config_1.listAccounts)();
|
|
186
|
+
const wanted = (0, config_1.slugFor)(emailOrSlug);
|
|
187
|
+
const match = accounts.find((a) => (0, config_1.slugFor)(a.email || '') === wanted) ||
|
|
188
|
+
accounts.find((a) => (0, config_1.slugFor)(a.email || '').startsWith(wanted));
|
|
189
|
+
if (!match) {
|
|
190
|
+
return (0, ui_1.handleError)(new Error(`No saved account matches "${emailOrSlug}".\n` +
|
|
191
|
+
` Saved: ${accounts.map((a) => a.email).join(', ') || '(none)'}\n` +
|
|
192
|
+
` Run \`cku accounts list\` first, or \`cku login\` to add one.`));
|
|
193
|
+
}
|
|
194
|
+
if (opts.global) {
|
|
195
|
+
(0, config_1.setActiveAccount)(match.email || emailOrSlug);
|
|
196
|
+
(0, ui_1.success)(`Default account set to ${match.email} (global — affects every unbound folder).`);
|
|
197
|
+
return warnIfSignedOut(match);
|
|
198
|
+
}
|
|
199
|
+
const path = (0, path_1.join)(process.cwd(), '.cku.json');
|
|
200
|
+
(0, config_1.bindProjectAccount)(match.email || emailOrSlug);
|
|
201
|
+
(0, ui_1.success)(`This folder now uses ${match.email}.`);
|
|
202
|
+
(0, ui_1.info)(` wrote ${path}`);
|
|
203
|
+
(0, ui_1.info)(' Commit it if the whole team should use the same account here.');
|
|
204
|
+
warnIfSignedOut(match);
|
|
205
|
+
}
|
|
206
|
+
/** `cku accounts unbind` — drop this folder's binding. */
|
|
207
|
+
async function accountsUnbindCommand() {
|
|
208
|
+
if ((0, config_1.unbindProjectAccount)())
|
|
209
|
+
(0, ui_1.success)('Removed this folder\'s binding. Now using the global default.');
|
|
210
|
+
else {
|
|
211
|
+
(0, ui_1.fail)('This folder has no .cku.json binding.');
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** `cku accounts status` — which account, and where the choice came from. */
|
|
216
|
+
async function accountsStatusCommand() {
|
|
217
|
+
const accounts = (0, config_1.listAccounts)();
|
|
218
|
+
const active = (0, config_1.activeAccount)();
|
|
219
|
+
const source = (0, config_1.accountSource)();
|
|
220
|
+
(0, ui_1.info)('Account resolution');
|
|
221
|
+
(0, ui_1.info)('');
|
|
222
|
+
(0, ui_1.info)(` in effect ${active ? accounts.find((a) => (0, config_1.slugFor)(a.email || '') === active)?.email || active : '(none)'}`);
|
|
223
|
+
(0, ui_1.info)(` from ${source === 'env' ? 'CK_ACCOUNT environment variable'
|
|
224
|
+
: source === 'project' ? `.cku.json in ${process.cwd()}`
|
|
225
|
+
: source === 'global' ? 'global default'
|
|
226
|
+
: 'nothing — no account selected'}`);
|
|
227
|
+
(0, ui_1.info)(` saved ${accounts.length} account(s)`);
|
|
228
|
+
if (source === 'global') {
|
|
229
|
+
(0, ui_1.info)('');
|
|
230
|
+
(0, ui_1.info)(' This is shared state: another agent running `cku accounts use` would change it.');
|
|
231
|
+
(0, ui_1.info)(' Run `cku accounts bind <email>` here to make this folder independent.');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/** `cku accounts use <email>` — make a stored account active. */
|
|
235
|
+
async function accountsUseCommand(emailOrSlug) {
|
|
236
|
+
const accounts = (0, config_1.listAccounts)();
|
|
237
|
+
const wanted = (0, config_1.slugFor)(emailOrSlug);
|
|
238
|
+
const match = accounts.find((a) => (0, config_1.slugFor)(a.email || '') === wanted) ||
|
|
239
|
+
accounts.find((a) => (0, config_1.slugFor)(a.email || '').startsWith(wanted));
|
|
240
|
+
if (!match) {
|
|
241
|
+
return (0, ui_1.handleError)(new Error(`No saved account matches "${emailOrSlug}".\n` +
|
|
242
|
+
` Saved: ${accounts.map((a) => a.email).join(', ') || '(none)'}\n` +
|
|
243
|
+
` Run \`cku accounts list\` to see them.`));
|
|
244
|
+
}
|
|
245
|
+
(0, config_1.setActiveAccount)(match.email || emailOrSlug);
|
|
246
|
+
(0, ui_1.success)(`Now using ${match.email}.`);
|
|
247
|
+
warnIfSignedOut(match);
|
|
248
|
+
}
|
|
249
|
+
/** `cku accounts remove <email> [--yes]` — forget a stored login. */
|
|
250
|
+
async function accountsRemoveCommand(emailOrSlug, opts) {
|
|
251
|
+
const accounts = (0, config_1.listAccounts)();
|
|
252
|
+
const wanted = (0, config_1.slugFor)(emailOrSlug);
|
|
253
|
+
const match = accounts.find((a) => (0, config_1.slugFor)(a.email || '') === wanted) ||
|
|
254
|
+
accounts.find((a) => (0, config_1.slugFor)(a.email || '').startsWith(wanted));
|
|
255
|
+
if (!match)
|
|
256
|
+
return (0, ui_1.handleError)(new Error(`No saved account matches "${emailOrSlug}".`));
|
|
257
|
+
if (!opts.yes) {
|
|
258
|
+
const { ok } = await (0, prompts_1.default)({
|
|
259
|
+
type: 'confirm',
|
|
260
|
+
name: 'ok',
|
|
261
|
+
message: `Remove the saved login for ${match.email}?`,
|
|
262
|
+
initial: false,
|
|
263
|
+
});
|
|
264
|
+
if (!ok)
|
|
265
|
+
return void (0, ui_1.info)('Cancelled.');
|
|
266
|
+
}
|
|
267
|
+
const slug = (0, config_1.slugFor)(match.email || '');
|
|
268
|
+
(0, config_1.removeAccount)(slug);
|
|
269
|
+
(0, ui_1.success)(`Removed ${match.email}.`);
|
|
270
|
+
// Leave .cku.json alone — it may be committed — but say it now dangles.
|
|
271
|
+
if ((0, config_1.projectAccount)() === slug) {
|
|
272
|
+
(0, ui_1.info)(` Warning: this folder's .cku.json still binds ${match.email}, which is no longer saved.`);
|
|
273
|
+
(0, ui_1.info)(' Run `cku accounts unbind` here, or `cku login` to add the account back.');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setInboundCommand = setInboundCommand;
|
|
4
|
+
exports.setOutboundCommand = setOutboundCommand;
|
|
5
|
+
const session_1 = require("../lib/session");
|
|
6
|
+
const ui_1 = require("../lib/ui");
|
|
7
|
+
/** Read the current inbound number ids off an agent detail response. */
|
|
8
|
+
function currentInboundIds(agent) {
|
|
9
|
+
const list = Array.isArray(agent?.inboundPhoneNumbers) ? agent.inboundPhoneNumbers : [];
|
|
10
|
+
return list.map((v) => (typeof v === 'object' ? v?._id : v)).filter(Boolean);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `cku agents set-inbound <agentId> --number <id> [--remove]`
|
|
14
|
+
*
|
|
15
|
+
* Reuses PUT /cli/agents/:id (full inbound array replacement). The backend
|
|
16
|
+
* enforces the one-inbound-agent-per-number rule and returns 409 on conflict.
|
|
17
|
+
*/
|
|
18
|
+
async function setInboundCommand(agentId, opts) {
|
|
19
|
+
const api = (0, session_1.requireApi)();
|
|
20
|
+
if (!opts.number)
|
|
21
|
+
return (0, ui_1.handleError)(new Error('--number <phoneNumberId> is required'));
|
|
22
|
+
try {
|
|
23
|
+
const { agent } = await api.getAgent(agentId);
|
|
24
|
+
const ids = new Set(currentInboundIds(agent));
|
|
25
|
+
if (opts.remove)
|
|
26
|
+
ids.delete(opts.number);
|
|
27
|
+
else
|
|
28
|
+
ids.add(opts.number);
|
|
29
|
+
await api.updateAgent(agentId, undefined, { inboundPhoneNumbers: [...ids] });
|
|
30
|
+
(0, ui_1.success)(`${opts.remove ? 'Removed' : 'Set'} inbound number on agent ${agentId}.`);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
(0, ui_1.handleError)(err);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** `cku agents set-outbound <agentId> --number <id>` (or `--clear`) */
|
|
37
|
+
async function setOutboundCommand(agentId, opts) {
|
|
38
|
+
const api = (0, session_1.requireApi)();
|
|
39
|
+
if (!opts.clear && !opts.number)
|
|
40
|
+
return (0, ui_1.handleError)(new Error('--number <phoneNumberId> or --clear is required'));
|
|
41
|
+
try {
|
|
42
|
+
await api.updateAgent(agentId, undefined, { outboundPhoneNumber: opts.clear ? null : opts.number });
|
|
43
|
+
(0, ui_1.success)(opts.clear ? `Cleared outbound number on agent ${agentId}.` : `Set outbound number on agent ${agentId}.`);
|
|
44
|
+
if (!opts.clear)
|
|
45
|
+
(0, ui_1.info)('Note: an outbound number may be shared by multiple agents.');
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
(0, ui_1.handleError)(err);
|
|
49
|
+
}
|
|
50
|
+
}
|