@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 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":";;;AAAA,+CAA6D;AAE7D;;;GAGG;AACH,MAAa,QAAS,SAAQ,KAAK;IAEf;IACA;IAEA;IAJlB,YACkB,MAAc,EACd,IAAY,EAC5B,OAAe,EACC,IAAc;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QALC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAQ;QAEZ,SAAI,GAAJ,IAAI,CAAU;QAG9B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAVD,4BAUC;AAeD,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B;;;;;;GAMG;AACH,MAAa,aAAa;IACP,KAAK,CAAc;IACnB,UAAU,CAAS;IAEpC,YAAY,OAAsB,EAAE;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,IAAI,IAAA,6BAAe,GAAE,CAAC;QACpD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,QAAQ,CAChB,GAAG,EACH,eAAe,EACf,iEAAiE,CAClE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,OAAO,CAAI,IAAY,EAAE,OAAuB,EAAE;QACtD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAgB;YACxB,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,KAAK;YAC5B,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;gBAC3C,cAAc,EAAE,kBAAkB;gBAClC,YAAY,EAAE,iBAAiB;aAChC;SACF,CAAC;QACF,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,OAA0B,CAAC;QAC/B,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;YAC5D,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACnC,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;oBACnD,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;oBAChC,SAAS;gBACX,CAAC;gBACD,OAAO,MAAM,aAAa,CAAI,GAAG,CAAC,CAAC;YACrC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,QAAQ;oBAAE,MAAM,GAAG,CAAC,CAAC,yBAAyB;gBACjE,OAAO,GAAG,GAAY,CAAC;gBACvB,IAAI,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;oBAC9B,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;oBAChC,SAAS;gBACX,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,IAAI,QAAQ,CAChB,CAAC,EACD,eAAe,EACf,4BAA4B,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,CACtF,CAAC;IACJ,CAAC;IAEO,QAAQ,CAAC,IAAY,EAAE,KAAmD;QAChF,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3C,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;oBAChC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;CACF;AArED,sCAqEC;AAED,KAAK,UAAU,aAAa,CAAI,GAAa;IAC3C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,IAAS,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;QACzD,MAAM,OAAO,GACX,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC;YACpB,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,8BAA8B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACjF,MAAM,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,IAAS,CAAC;AACnB,CAAC;AAED,SAAS,SAAS,CAAC,OAAe;IAChC,2BAA2B;IAC3B,OAAO,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,oDAsDC;AAxED,mDAAqC;AAErC,sCAAoD;AACpD,gDAAiD;AACjD,sCAAuC;AACvC,sCAAiE;AASjE;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,OAAgB,EAAE,UAA4B;IACjF,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,qDAAqD,CAAC;SAClE,MAAM,CAAC,iBAAiB,EAAE,8CAA8C,CAAC;SACzE,MAAM,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;SACtD,MAAM,CAAC,KAAK,EAAE,OAAqD,EAAE,EAAE;QACtE,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;YAC1B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,IAAI,OAAO,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;oBACnD,MAAM,IAAI,iBAAQ,CAChB,GAAG,EACH,gBAAgB,EAChB,8DAA8D,CAC/D,CAAC;gBACJ,CAAC;gBACD,KAAK,GAAG,MAAM,WAAW,EAAE,CAAC;YAC9B,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YACrB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,iBAAQ,CAAC,GAAG,EAAE,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;YACtE,CAAC;YAED,+DAA+D;YAC/D,MAAM,MAAM,GAAG,IAAI,sBAAa,CAAC;gBAC/B,WAAW,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,IAAA,mBAAU,GAAE,EAAE;gBAChD,UAAU,EAAE,CAAC;aACd,CAAC,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,OAAO,CAAiB,iBAAiB,CAAC,CAAC;YAEnE,IAAA,6BAAe,EAAC;gBACd,KAAK;gBACL,UAAU,EAAE,IAAA,mBAAU,GAAE;gBACxB,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,SAAS;gBACrC,cAAc,EAAE,EAAE,CAAC,YAAY,CAAC,EAAE;gBAClC,gBAAgB,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI;aACvC,CAAC,CAAC;YAEH,IAAA,qBAAY,EACV;gBACE,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE,EAAE,CAAC,IAAI;gBACb,YAAY,EAAE,EAAE,CAAC,YAAY;gBAC7B,KAAK,EAAE,EAAE,CAAC,KAAK;aAChB,EACD,gBAAgB,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,YAAY,CAAC,IAAI,KAAK,EAAE,CAAC,YAAY,CAAC,IAAI,yCAAyC,EACtI,IAAI,CACL,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,IAAA,mBAAU,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACtF,EAAE,CAAC,QAAQ,CAAC,uCAAuC,EAAE,CAAC,MAAM,EAAE,EAAE;YAC9D,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}