@sente-labs/cli 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 +88 -0
- package/dist/client.js +114 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/init.js +201 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.js +100 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/project.js +168 -0
- package/dist/commands/project.js.map +1 -0
- package/dist/commands/task.js +364 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/whoami.js +32 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.js +90 -0
- package/dist/credentials.js.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/output.js +54 -0
- package/dist/output.js.map +1 -0
- package/dist/skill/SKILL.md +116 -0
- package/dist/skill/template.js +54 -0
- package/dist/skill/template.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @sente-labs/cli
|
|
2
|
+
|
|
3
|
+
The Sente CLI -- author and manage QA tests from your terminal and Claude Code. Tests run against your staging URL after each deploy.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @sente-labs/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Get a token from https://app.sente.run -> Settings -> API Tokens
|
|
17
|
+
sente login
|
|
18
|
+
|
|
19
|
+
# 2. In your repo, create a Sente project and install the Claude Code skill
|
|
20
|
+
cd my-app
|
|
21
|
+
sente init --staging-url https://staging.my-app.com
|
|
22
|
+
|
|
23
|
+
# 3. Open Claude Code; it auto-discovers the skill
|
|
24
|
+
claude
|
|
25
|
+
|
|
26
|
+
# 4. Ask CC: "Write a Sente test for the login flow"
|
|
27
|
+
# CC drafts steps, validates them, and creates the task via the CLI.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The skill installs to `~/.claude/skills/sente/SKILL.md`. Re-install or update
|
|
31
|
+
with `sente install-skill`.
|
|
32
|
+
|
|
33
|
+
## Where do tests run?
|
|
34
|
+
|
|
35
|
+
Sente runs in a cloud-hosted Chrome browser, so it only operates against
|
|
36
|
+
**publicly-reachable URLs**. Staging environments, production, and public
|
|
37
|
+
preview deploys (Vercel, Netlify) all work. `localhost` and non-public PR
|
|
38
|
+
previews do not.
|
|
39
|
+
|
|
40
|
+
Tests are triggered after each staging deploy via the per-project deploy
|
|
41
|
+
webhook. Configure it in **Project Settings -> Deploy Webhook**, then POST
|
|
42
|
+
to it from CI:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
- run: |
|
|
46
|
+
curl -X POST $SENTE_DEPLOY_ENDPOINT \
|
|
47
|
+
-H "X-Sente-Secret: ${{ secrets.SENTE_WEBHOOK_SECRET }}" \
|
|
48
|
+
-d '{"environment":"staging","commitSha":"'"$GITHUB_SHA"'"}'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Common commands
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
sente whoami # show current user + org
|
|
55
|
+
sente project show # show project bound to this repo
|
|
56
|
+
sente task list --json # list tasks
|
|
57
|
+
sente task show <id> # task details
|
|
58
|
+
sente task results <id> --failed-step --json # focused failure payload
|
|
59
|
+
sente task create --workflow <id> --name "..." --instruction "..." \
|
|
60
|
+
--steps-from steps.json
|
|
61
|
+
sente task update <id> --steps-from steps.json
|
|
62
|
+
sente task delete <id>
|
|
63
|
+
sente sync # regenerate .sente/tasks.json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use `--json` on any command for machine-readable output -- this is what
|
|
67
|
+
the Claude Code skill consumes.
|
|
68
|
+
|
|
69
|
+
## Auth
|
|
70
|
+
|
|
71
|
+
Tokens are stored in `~/.sente/credentials` (mode 0600). You can override
|
|
72
|
+
both the token and the gateway URL via env:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export SENTE_API_TOKEN=sk_sente_...
|
|
76
|
+
export SENTE_GATEWAY_URL=https://api.sente.run
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Useful for CI where interactive `sente login` isn't an option.
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
- Home: <https://sente.run>
|
|
84
|
+
- Issues: <https://github.com/sente-labs/agentic-browser/issues>
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
UNLICENSED. Contact support@sente.run for licensing terms.
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GatewayClient = exports.ApiError = void 0;
|
|
4
|
+
const credentials_1 = require("./credentials");
|
|
5
|
+
/**
|
|
6
|
+
* Domain-specific error. Surfaces structured failure modes so commands can
|
|
7
|
+
* print clean messages without leaking HTTP internals.
|
|
8
|
+
*/
|
|
9
|
+
class ApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
code;
|
|
12
|
+
body;
|
|
13
|
+
constructor(status, code, message, body) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.body = body;
|
|
18
|
+
this.name = 'ApiError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.ApiError = ApiError;
|
|
22
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
23
|
+
/**
|
|
24
|
+
* Thin HTTP client around fetch (Node 18+). Injects the PAT, handles retries
|
|
25
|
+
* with exponential backoff on 5xx, and normalizes errors into ApiError.
|
|
26
|
+
*
|
|
27
|
+
* Does NOT send x-organization-id -- the PAT is bound to a single org at
|
|
28
|
+
* issuance, and the gateway reads org from the token, not the header.
|
|
29
|
+
*/
|
|
30
|
+
class GatewayClient {
|
|
31
|
+
creds;
|
|
32
|
+
maxRetries;
|
|
33
|
+
constructor(opts = {}) {
|
|
34
|
+
const creds = opts.credentials ?? (0, credentials_1.loadCredentials)();
|
|
35
|
+
if (!creds) {
|
|
36
|
+
throw new ApiError(401, 'NOT_LOGGED_IN', 'Not logged in. Run `sente login` first, or set SENTE_API_TOKEN.');
|
|
37
|
+
}
|
|
38
|
+
this.creds = creds;
|
|
39
|
+
this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
40
|
+
}
|
|
41
|
+
async request(path, opts = {}) {
|
|
42
|
+
const url = this.buildUrl(path, opts.query);
|
|
43
|
+
const init = {
|
|
44
|
+
method: opts.method ?? 'GET',
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${this.creds.token}`,
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'User-Agent': 'sente-cli/0.1.0',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
if (opts.body !== undefined) {
|
|
52
|
+
init.body = JSON.stringify(opts.body);
|
|
53
|
+
}
|
|
54
|
+
let lastErr;
|
|
55
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(url, init);
|
|
58
|
+
if (res.status >= 500 && attempt < this.maxRetries) {
|
|
59
|
+
await sleep(backoffMs(attempt));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
return await parseResponse(res);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof ApiError)
|
|
66
|
+
throw err; // 4xx errors don't retry
|
|
67
|
+
lastErr = err;
|
|
68
|
+
if (attempt < this.maxRetries) {
|
|
69
|
+
await sleep(backoffMs(attempt));
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new ApiError(0, 'NETWORK_ERROR', `Network error contacting ${this.creds.gatewayUrl}: ${lastErr?.message ?? 'unknown'}`);
|
|
75
|
+
}
|
|
76
|
+
buildUrl(path, query) {
|
|
77
|
+
const base = this.creds.gatewayUrl.replace(/\/$/, '');
|
|
78
|
+
const url = new URL(`${base}${path}`);
|
|
79
|
+
if (query) {
|
|
80
|
+
for (const [k, v] of Object.entries(query)) {
|
|
81
|
+
if (v !== undefined && v !== '') {
|
|
82
|
+
url.searchParams.set(k, String(v));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return url.toString();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.GatewayClient = GatewayClient;
|
|
90
|
+
async function parseResponse(res) {
|
|
91
|
+
const text = await res.text();
|
|
92
|
+
let body;
|
|
93
|
+
try {
|
|
94
|
+
body = text ? JSON.parse(text) : undefined;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
body = text;
|
|
98
|
+
}
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
const code = (body && body.code) || `HTTP_${res.status}`;
|
|
101
|
+
const message = (body && body.error) ||
|
|
102
|
+
(typeof body === 'string' ? body : `Request failed with status ${res.status}`);
|
|
103
|
+
throw new ApiError(res.status, code, message, body);
|
|
104
|
+
}
|
|
105
|
+
return body;
|
|
106
|
+
}
|
|
107
|
+
function backoffMs(attempt) {
|
|
108
|
+
// 200ms, 400ms, 800ms, ...
|
|
109
|
+
return 200 * Math.pow(2, attempt);
|
|
110
|
+
}
|
|
111
|
+
function sleep(ms) {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerInitCommand = registerInitCommand;
|
|
37
|
+
exports.registerInstallSkillCommand = registerInstallSkillCommand;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const readline = __importStar(require("readline"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const client_1 = require("../client");
|
|
44
|
+
const output_1 = require("../output");
|
|
45
|
+
const template_1 = require("../skill/template");
|
|
46
|
+
const GITIGNORE_BLOCK_MARKER = '# Sente CLI -- per-developer state';
|
|
47
|
+
const LAST_CHECKED_LINE = '.sente/last-checked.json';
|
|
48
|
+
function registerInitCommand(program, globalOpts) {
|
|
49
|
+
program
|
|
50
|
+
.command('init')
|
|
51
|
+
.description('Create a Sente project for this repo and install the Claude Code skill')
|
|
52
|
+
.option('--name <name>', 'Project name (defaults to the repo directory name)')
|
|
53
|
+
.option('--staging-url <url>', 'Public staging URL of the deployed app')
|
|
54
|
+
.option('--workflow <name>', 'Default workflow name', 'Smoke tests')
|
|
55
|
+
.option('--skip-skill', 'Do not install the Claude Code skill')
|
|
56
|
+
.option('--non-interactive', 'Fail rather than prompt')
|
|
57
|
+
.action(async (cmdOpts) => {
|
|
58
|
+
const opts = globalOpts();
|
|
59
|
+
try {
|
|
60
|
+
const cwd = process.cwd();
|
|
61
|
+
if (fs.existsSync(path.join(cwd, '.sente', 'config.json'))) {
|
|
62
|
+
throw new Error('A Sente project already exists in this repo (.sente/config.json found). Use `sente sync` to refresh tasks, or delete .sente/ first.');
|
|
63
|
+
}
|
|
64
|
+
// Resolve inputs. Interactive prompts only when TTY + not --non-interactive
|
|
65
|
+
// and missing values. CC always invokes with flags + --non-interactive.
|
|
66
|
+
const interactive = !cmdOpts.nonInteractive && !(0, output_1.isJson)(opts) && process.stdin.isTTY;
|
|
67
|
+
const defaultName = deriveDefaultProjectName(cwd);
|
|
68
|
+
const projectName = cmdOpts.name ?? (interactive ? await prompt('Project name', defaultName) : defaultName);
|
|
69
|
+
const stagingUrl = cmdOpts.stagingUrl ?? (interactive ? await prompt('Staging URL (https://...)', '') : '');
|
|
70
|
+
if (!stagingUrl) {
|
|
71
|
+
throw new Error('Staging URL is required. Pass --staging-url <url>, or run interactively. Sente only runs against publicly-reachable URLs.');
|
|
72
|
+
}
|
|
73
|
+
if (!/^https?:\/\//.test(stagingUrl)) {
|
|
74
|
+
throw new Error('Staging URL must start with http:// or https://');
|
|
75
|
+
}
|
|
76
|
+
const workflowName = cmdOpts.workflow ?? 'Smoke tests';
|
|
77
|
+
const client = new client_1.GatewayClient();
|
|
78
|
+
// 1. Create the project.
|
|
79
|
+
const projectRes = await client.request('/api/projects', {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
body: { name: projectName, baseUrl: stagingUrl },
|
|
82
|
+
});
|
|
83
|
+
const project = projectRes.project;
|
|
84
|
+
// 2. Create the default workflow inside the new project.
|
|
85
|
+
const workflowRes = await client.request(`/api/projects/${project.id}/workflows`, { method: 'POST', body: { name: workflowName } });
|
|
86
|
+
const workflow = workflowRes.workflow;
|
|
87
|
+
// 3. Write repo-local files. Atomic order: ensure .sente exists first.
|
|
88
|
+
const senteDir = path.join(cwd, '.sente');
|
|
89
|
+
fs.mkdirSync(senteDir, { recursive: true });
|
|
90
|
+
const config = {
|
|
91
|
+
projectId: project.id,
|
|
92
|
+
gatewayUrl: defaultGatewayBase(),
|
|
93
|
+
baseUrl: stagingUrl,
|
|
94
|
+
workflowId: workflow.id,
|
|
95
|
+
};
|
|
96
|
+
const tasks = { projectId: project.id, tasks: [] };
|
|
97
|
+
fs.writeFileSync(path.join(senteDir, 'config.json'), JSON.stringify(config, null, 2) + '\n');
|
|
98
|
+
fs.writeFileSync(path.join(senteDir, 'tasks.json'), JSON.stringify(tasks, null, 2) + '\n');
|
|
99
|
+
// 4. Add per-developer state files to .gitignore (idempotent).
|
|
100
|
+
ensureGitignoreEntry(cwd);
|
|
101
|
+
// 5. Install the Claude Code skill (unless explicitly skipped).
|
|
102
|
+
let skillPath = null;
|
|
103
|
+
if (!cmdOpts.skipSkill) {
|
|
104
|
+
skillPath = installSkill();
|
|
105
|
+
}
|
|
106
|
+
(0, output_1.printSuccess)({
|
|
107
|
+
project,
|
|
108
|
+
workflow,
|
|
109
|
+
configPath: path.join('.sente', 'config.json'),
|
|
110
|
+
tasksPath: path.join('.sente', 'tasks.json'),
|
|
111
|
+
skillPath,
|
|
112
|
+
}, () => {
|
|
113
|
+
process.stdout.write(`Created project: ${project.name} (${project.id})\n`);
|
|
114
|
+
process.stdout.write(`Created workflow: ${workflow.name} (${workflow.id})\n`);
|
|
115
|
+
process.stdout.write(`Wrote .sente/config.json and .sente/tasks.json\n`);
|
|
116
|
+
if (skillPath) {
|
|
117
|
+
process.stdout.write(`Installed Claude Code skill at ${skillPath}\n`);
|
|
118
|
+
}
|
|
119
|
+
process.stdout.write(`\nNext: open Claude Code in this repo and ask it to draft a Sente test for one of your flows.\n`);
|
|
120
|
+
}, opts);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err instanceof client_1.ApiError && err.status === 0) {
|
|
124
|
+
// Surfaces gateway-unreachable cleanly.
|
|
125
|
+
process.exit((0, output_1.printError)(err, opts));
|
|
126
|
+
}
|
|
127
|
+
process.exit((0, output_1.printError)(err, opts));
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function registerInstallSkillCommand(program, globalOpts) {
|
|
132
|
+
program
|
|
133
|
+
.command('install-skill')
|
|
134
|
+
.description('Install or update the Claude Code skill at ~/.claude/skills/sente/SKILL.md')
|
|
135
|
+
.action(() => {
|
|
136
|
+
const opts = globalOpts();
|
|
137
|
+
try {
|
|
138
|
+
const skillPath = installSkill();
|
|
139
|
+
(0, output_1.printSuccess)({ skillPath }, `Installed Claude Code skill at ${skillPath}`, opts);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
process.exit((0, output_1.printError)(err, opts));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Write the bundled SKILL.md to the standard Claude Code skill path.
|
|
148
|
+
* Returns the absolute path.
|
|
149
|
+
*/
|
|
150
|
+
function installSkill() {
|
|
151
|
+
const dest = (0, template_1.skillInstallPath)(os.homedir());
|
|
152
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
153
|
+
fs.writeFileSync(dest, template_1.SKILL_MD);
|
|
154
|
+
return dest;
|
|
155
|
+
}
|
|
156
|
+
function deriveDefaultProjectName(cwd) {
|
|
157
|
+
// Prefer the git repo's top-level directory name; fall back to cwd's basename.
|
|
158
|
+
try {
|
|
159
|
+
const top = (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
|
|
160
|
+
cwd,
|
|
161
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
162
|
+
})
|
|
163
|
+
.toString()
|
|
164
|
+
.trim();
|
|
165
|
+
if (top)
|
|
166
|
+
return path.basename(top);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// not a git repo
|
|
170
|
+
}
|
|
171
|
+
return path.basename(cwd);
|
|
172
|
+
}
|
|
173
|
+
function defaultGatewayBase() {
|
|
174
|
+
return process.env.SENTE_GATEWAY_URL || 'https://api.sente.run';
|
|
175
|
+
}
|
|
176
|
+
function ensureGitignoreEntry(cwd) {
|
|
177
|
+
const gitignorePath = path.join(cwd, '.gitignore');
|
|
178
|
+
let existing = '';
|
|
179
|
+
try {
|
|
180
|
+
existing = fs.readFileSync(gitignorePath, 'utf8');
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// file doesn't exist yet
|
|
184
|
+
}
|
|
185
|
+
if (existing.includes(LAST_CHECKED_LINE))
|
|
186
|
+
return;
|
|
187
|
+
const block = `\n${GITIGNORE_BLOCK_MARKER}\n${LAST_CHECKED_LINE}\n`;
|
|
188
|
+
// Append; do not rewrite the file structure.
|
|
189
|
+
fs.writeFileSync(gitignorePath, existing.endsWith('\n') || existing.length === 0 ? existing + block : existing + '\n' + block);
|
|
190
|
+
}
|
|
191
|
+
function prompt(question, fallback) {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
194
|
+
const suffix = fallback ? ` [${fallback}]` : '';
|
|
195
|
+
rl.question(`${question}${suffix}: `, (answer) => {
|
|
196
|
+
rl.close();
|
|
197
|
+
resolve(answer.trim() || fallback);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerLoginCommand = registerLoginCommand;
|
|
37
|
+
const readline = __importStar(require("readline"));
|
|
38
|
+
const client_1 = require("../client");
|
|
39
|
+
const credentials_1 = require("../credentials");
|
|
40
|
+
const config_1 = require("../config");
|
|
41
|
+
const output_1 = require("../output");
|
|
42
|
+
/**
|
|
43
|
+
* Prompt for a PAT (or accept it via --token / stdin), validate it against
|
|
44
|
+
* /api/cli/whoami, and persist credentials to ~/.sente/credentials.
|
|
45
|
+
*/
|
|
46
|
+
function registerLoginCommand(program, globalOpts) {
|
|
47
|
+
program
|
|
48
|
+
.command('login')
|
|
49
|
+
.description('Store a Sente Personal Access Token and validate it')
|
|
50
|
+
.option('--token <token>', 'PAT value (otherwise prompted interactively)')
|
|
51
|
+
.option('--non-interactive', 'Fail rather than prompt')
|
|
52
|
+
.action(async (cmdOpts) => {
|
|
53
|
+
const opts = globalOpts();
|
|
54
|
+
try {
|
|
55
|
+
let token = cmdOpts.token;
|
|
56
|
+
if (!token) {
|
|
57
|
+
if (cmdOpts.nonInteractive || !process.stdin.isTTY) {
|
|
58
|
+
throw new client_1.ApiError(400, 'TOKEN_REQUIRED', 'No token provided. Use --token <value> or run interactively.');
|
|
59
|
+
}
|
|
60
|
+
token = await promptToken();
|
|
61
|
+
}
|
|
62
|
+
token = token.trim();
|
|
63
|
+
if (!token) {
|
|
64
|
+
throw new client_1.ApiError(400, 'TOKEN_REQUIRED', 'Token cannot be empty.');
|
|
65
|
+
}
|
|
66
|
+
// Validate by hitting /api/cli/whoami with the provided token.
|
|
67
|
+
const client = new client_1.GatewayClient({
|
|
68
|
+
credentials: { token, gatewayUrl: (0, config_1.gatewayUrl)() },
|
|
69
|
+
maxRetries: 1,
|
|
70
|
+
});
|
|
71
|
+
const me = await client.request('/api/cli/whoami');
|
|
72
|
+
(0, credentials_1.saveCredentials)({
|
|
73
|
+
token,
|
|
74
|
+
gatewayUrl: (0, config_1.gatewayUrl)(),
|
|
75
|
+
userEmail: me.user.email ?? undefined,
|
|
76
|
+
organizationId: me.organization.id,
|
|
77
|
+
organizationName: me.organization.name,
|
|
78
|
+
});
|
|
79
|
+
(0, output_1.printSuccess)({
|
|
80
|
+
ok: true,
|
|
81
|
+
user: me.user,
|
|
82
|
+
organization: me.organization,
|
|
83
|
+
token: me.token,
|
|
84
|
+
}, `Logged in as ${me.user.email ?? me.user.id} (${me.organization.name}, ${me.organization.role}). Token saved to ~/.sente/credentials.`, opts);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
process.exit((0, output_1.printError)(err, opts));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function promptToken() {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
94
|
+
rl.question('Paste your Sente PAT (sk_sente_...): ', (answer) => {
|
|
95
|
+
rl.close();
|
|
96
|
+
resolve(answer);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|