@canner-ca/cli 0.3.0 → 0.4.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 (3) hide show
  1. package/README.md +51 -3
  2. package/package.json +1 -1
  3. package/src/cli.mjs +57 -7
package/README.md CHANGED
@@ -38,6 +38,7 @@ canner deploy --follow
38
38
  | Command | What it does |
39
39
  |---|---|
40
40
  | `canner login` | Prompt for an API token, store it at `~/.canner/credentials` (mode 0600). |
41
+ | `canner logout` | Delete `~/.canner/credentials` from this machine. |
41
42
  | `canner whoami` | Show the email + plan this CLI is signed in as. |
42
43
 
43
44
  ### Projects
@@ -148,12 +149,59 @@ canner deploy --follow
148
149
 
149
150
  The CLI uploads under the same per-tier compressed-archive cap as the dashboard:
150
151
 
151
- - **Starter** — 50 MB
152
- - **Basic** — 200 MB
153
- - **Pro** — 500 MB
152
+ <!-- BEGIN:GENERATED:cli-caps -->
153
+ - **Starter** — 100 MB
154
+ - **Live** — 500 MB
155
+ - **Dedicated** — 1 GB
156
+ <!-- END:GENERATED:cli-caps -->
154
157
 
155
158
  If your project is bigger than that compressed, drop heavier directories into `.cannerignore` or upgrade your plan. `node_modules` is already excluded; the typical thing that pushes a project over the cap is a `public/` folder full of media.
156
159
 
160
+ ## What this CLI stores and sends
161
+
162
+ **Written to your machine — two files, both plain text you can read:**
163
+
164
+ | Path | Contents | Written by |
165
+ |---|---|---|
166
+ | `~/.canner/credentials` | `{ api_base, token }`, mode `0600` | `canner login` |
167
+ | `./canner.json` | Your project slug, in the project directory | `canner init` |
168
+
169
+ Nothing else is written. No system settings, shell profiles, PATH entries, or
170
+ startup items are modified.
171
+
172
+ **Sent over the network:** requests go only to `https://api.canner.ca` (or
173
+ whatever `CANNER_API_BASE` is set to). `canner deploy` uploads a tar of your
174
+ project directory — minus `node_modules`, `.git`, build output, and anything in
175
+ your `.cannerignore` — so that we can build and host it.
176
+
177
+ **Telemetry: none.** There is no usage tracking, analytics, crash reporting, or
178
+ phone-home of any kind. The CLI makes no network request you didn't ask for by
179
+ running a command.
180
+
181
+ ## Removing the CLI
182
+
183
+ ```bash
184
+ canner logout # delete ~/.canner/credentials
185
+ npm uninstall -g @canner-ca/cli # remove the package
186
+ ```
187
+
188
+ `canner logout` removes the stored credential from **this machine only**. The
189
+ token itself stays valid — the same token may be in use in CI or on another
190
+ machine, so logging out on your laptop deliberately doesn't kill it. To revoke
191
+ it everywhere, run `canner token revoke <name>` before logging out, or revoke it
192
+ from your dashboard's API tokens page.
193
+
194
+ If you've already uninstalled the package and want to clear the leftover
195
+ credential, delete `~/.canner/credentials` by hand. Removing it affects nothing
196
+ else; `canner.json` in a project directory is just a slug and is safe to keep or
197
+ delete.
198
+
199
+ ## Terms
200
+
201
+ Use of the CLI and the Canner service is governed by the
202
+ [Terms of Service](https://canner.ca/terms) and the
203
+ [Privacy Policy](https://canner.ca/privacy).
204
+
157
205
  ## License
158
206
 
159
207
  MIT.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canner-ca/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Canner deploy CLI — git push from any terminal, no GitHub required",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -9,6 +9,7 @@
9
9
  //
10
10
  // Commands:
11
11
  // canner login store an API token
12
+ // canner logout remove the stored token from this machine
12
13
  // canner whoami show the account this CLI is signed in as
13
14
  // canner init [--slug X] create a project, write canner.json
14
15
  // canner deploy [path] [--slug X] [--follow]
@@ -27,7 +28,7 @@ import { randomUUID } from 'node:crypto';
27
28
  import { stdin as input, stdout as output } from 'node:process';
28
29
  import { create as tarCreate } from 'tar';
29
30
 
30
- const VERSION = '0.3.0';
31
+ const VERSION = '0.4.0';
31
32
  const DEFAULT_API_BASE = 'https://api.canner.ca';
32
33
  const CONFIG_DIR = join(homedir(), '.canner');
33
34
  const CREDENTIALS_PATH = join(CONFIG_DIR, 'credentials');
@@ -311,6 +312,53 @@ async function cmdWhoami() {
311
312
  }
312
313
  }
313
314
 
315
+ // Remove the stored credential from this machine.
316
+ //
317
+ // `npm uninstall -g @canner-ca/cli` leaves ~/.canner/credentials behind — a
318
+ // live bearer token sitting in the user's home directory with no obvious way
319
+ // to clear it. That's the gap this closes: uninstalling should not leave a
320
+ // working credential on disk.
321
+ //
322
+ // Deliberately local-only. We delete the file we wrote; we do NOT revoke the
323
+ // token server-side, because the same token may be in use by CI, the MCP
324
+ // server, or another machine, and silently killing those from a `logout` on
325
+ // one laptop would be a surprising side effect. The output says so plainly and
326
+ // points at `canner token revoke` for people who do want it gone everywhere.
327
+ async function cmdLogout() {
328
+ const creds = await loadCredentials();
329
+ let removed = false;
330
+ try {
331
+ await unlink(CREDENTIALS_PATH);
332
+ removed = true;
333
+ } catch (err) {
334
+ // ENOENT just means there was nothing stored — that's a success, not an
335
+ // error. Anything else (EACCES, EPERM) the user needs to know about.
336
+ if (err?.code !== 'ENOENT') {
337
+ die(`could not remove ${CREDENTIALS_PATH}: ${err.message}`);
338
+ }
339
+ }
340
+ if (JSON_MODE) {
341
+ return emitJSON({ removed, path: CREDENTIALS_PATH });
342
+ }
343
+ if (!removed) {
344
+ info('Nothing to remove — no stored credential on this machine.');
345
+ // A token in the environment survives logout, and a user who doesn't know
346
+ // that will think they've signed out when they haven't.
347
+ if (process.env.CANNER_TOKEN) {
348
+ muted('Note: CANNER_TOKEN is set in your environment and still applies.');
349
+ }
350
+ return;
351
+ }
352
+ info(`${green('Signed out.')} Removed ${CREDENTIALS_PATH}`);
353
+ if (creds.token) {
354
+ muted('The token itself is still valid. To revoke it everywhere, run');
355
+ muted(` canner token revoke <name> ${dim('(or use the dashboard)')}`);
356
+ }
357
+ if (process.env.CANNER_TOKEN) {
358
+ muted('Note: CANNER_TOKEN is set in your environment and still applies.');
359
+ }
360
+ }
361
+
314
362
  async function cmdInit(args) {
315
363
  const cwd = process.cwd();
316
364
  const existing = await loadProjectConfig(cwd);
@@ -342,7 +390,7 @@ async function cmdInit(args) {
342
390
  if (!body) die('project creation returned no body.');
343
391
  await writeProjectConfig(cwd, { slug });
344
392
  info(`${green('Created')} ${bold(slug)}`);
345
- muted(`Will be served at https://${slug}.app.canner.ca after the first deploy.`);
393
+ muted(`Will be served at https://${slug}.canner.app after the first deploy.`);
346
394
  muted(`Wrote ${PROJECT_CONFIG_FILE}. Run \`canner deploy\` to ship.`);
347
395
  }
348
396
 
@@ -400,7 +448,7 @@ async function cmdProjects() {
400
448
  }
401
449
  info(`${pad(bold('SLUG'), 24)}${pad(bold('STATUS'), 14)}${pad(bold('URL'), 40)}${bold('LAST REQUEST')}`);
402
450
  for (const p of rows) {
403
- const url = `https://${p.slug}.app.canner.ca`;
451
+ const url = `https://${p.slug}.canner.app`;
404
452
  const lastReq = relTime(p.last_request_at);
405
453
  info(`${pad(p.slug, 24)}${pad(colorStatus(p.status), 14)}${pad(dim(url), 40)}${dim(lastReq)}`);
406
454
  }
@@ -419,7 +467,7 @@ async function cmdStatus(args) {
419
467
  if (JSON_MODE) return emitJSON(p);
420
468
  info(`${bold(p.slug)} ${dim(`(${p.name ?? ''})`)}`);
421
469
  info(` status: ${colorStatus(p.status)}${p.is_cold ? dim(' (cold)') : ''}`);
422
- info(` url: ${cyan(`https://${p.slug}.app.canner.ca`)}`);
470
+ info(` url: ${cyan(`https://${p.slug}.canner.app`)}`);
423
471
  info(` source: ${p.is_connected ? `${dim('github')} ${p.repo_url ?? ''} (branch ${p.repo_branch})` : dim('upload-only')}`);
424
472
  info(` memory: ${p.memory_limit_mb}M${p.always_warm ? ` ${dim('(always-warm)')}` : ''}`);
425
473
  info(` last request: ${relTime(p.last_request_at)}`);
@@ -809,7 +857,7 @@ async function cmdOpen(args) {
809
857
  const dashboardOrigin = (creds.apiBase || DEFAULT_API_BASE).replace(/^https?:\/\/api\./, 'https://');
810
858
  const url = args.dashboard
811
859
  ? `${dashboardOrigin}/dashboard/projects/${slug}`
812
- : `https://${slug}.app.canner.ca`;
860
+ : `https://${slug}.canner.app`;
813
861
  info(`opening ${dim(url)}`);
814
862
  const { spawn } = await import('node:child_process');
815
863
  const platform = process.platform;
@@ -913,7 +961,7 @@ async function cmdDeploy(args) {
913
961
  info(`${dim('packaged')} ${fileCount} files (${sizeMb} MB compressed)`);
914
962
 
915
963
  // Upload. fs.openAsBlob() backs the Blob by the on-disk file lazily —
916
- // bytes only flow when fetch consumes them, so a 500 MB Pro upload
964
+ // bytes only flow when fetch consumes them, so a 500 MB Live upload
917
965
  // doesn't buffer into RAM on the user's machine. Node fetch has no
918
966
  // upload-progress hook as of 2026, so we just print the size we're
919
967
  // sending; the dashboard shows real-time progress for browser uploads.
@@ -1011,7 +1059,7 @@ async function tailDeploymentLogs(creds, slug, deploymentId) {
1011
1059
  }
1012
1060
 
1013
1061
  if (lastStatus === 'live') {
1014
- info(`\n${green('live')} at https://${slug}.app.canner.ca`);
1062
+ info(`\n${green('live')} at https://${slug}.canner.app`);
1015
1063
  return 0;
1016
1064
  }
1017
1065
  if (lastStatus === 'failed') {
@@ -1078,6 +1126,7 @@ ${bold('Usage:')}
1078
1126
 
1079
1127
  ${bold('Setup:')}
1080
1128
  ${bold('login')} Store your Canner API token (one-time).
1129
+ ${bold('logout')} Remove the stored token from this machine.
1081
1130
  ${bold('whoami')} Show the account this CLI is signed in as.
1082
1131
 
1083
1132
  ${bold('Projects:')}
@@ -1175,6 +1224,7 @@ if (!command || args.help || args.h || command === 'help') {
1175
1224
 
1176
1225
  const dispatch = {
1177
1226
  login: cmdLogin,
1227
+ logout: cmdLogout,
1178
1228
  whoami: cmdWhoami,
1179
1229
  init: cmdInit,
1180
1230
  deploy: cmdDeploy,