@michaelschnyder/teams-cli 0.2.0-canary.7.1.g64541e1a → 0.2.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 CHANGED
@@ -109,7 +109,7 @@ Person, chat, channel, and message commands support `--json`. JSON payloads stay
109
109
  | Command | Purpose | Examples and details |
110
110
  | --- | --- | --- |
111
111
  | `login` | Sign in using the default session; alias for `auth login` | [Authentication](docs/use/authentication.md) |
112
- | `version` | Inspect build provenance, check for updates, or select the notification channel | [Installation and upgrades](docs/use/installation.md) |
112
+ | `version` | Inspect build provenance, install updates, or switch stable/canary channels | [Installation and upgrades](docs/use/installation.md) |
113
113
  | `skills` | List, locate, install, and refresh the packaged agent skill | [Agent skill installation](docs/use/agent-skills.md) |
114
114
  | `doctor` | Diagnose the local runtime, browser, session, and managed skills without changing them | [Installation and upgrades](docs/use/installation.md#troubleshooting) |
115
115
  | `auth` | Login, refresh, inspect, export tokens, or logout | [Authentication](docs/use/authentication.md) |
@@ -1,34 +1,32 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.2.0-canary.7.1.g64541e1a",
4
- "channel": "canary",
5
- "builtAt": "2026-09-04T10:54:53.370Z",
3
+ "version": "0.2.0",
4
+ "channel": "stable",
5
+ "builtAt": "2026-09-04T11:47:26.658Z",
6
6
  "source": {
7
- "branch": "codex/fix-canary-provenance-race",
8
- "commit": "64541e1a22a00f539a23ee66d67aa70d33f47f8c",
9
- "commitUrl": "https://github.com/michaelschnyder/teams-cli/commit/64541e1a22a00f539a23ee66d67aa70d33f47f8c",
10
- "pullRequest": 14,
11
- "pullRequestUrl": "https://github.com/michaelschnyder/teams-cli/pull/14",
12
- "author": "michaelschnyder"
7
+ "branch": "main",
8
+ "commit": "0f109289ab1398dbf4c8717bb91df07588a96bc0",
9
+ "commitUrl": "https://github.com/michaelschnyder/teams-cli/commit/0f109289ab1398dbf4c8717bb91df07588a96bc0",
10
+ "author": "Michael Schnyder <michael.schnyder@outlook.com>"
13
11
  },
14
12
  "trigger": {
15
- "kind": "merged-pull-request",
13
+ "kind": "release",
16
14
  "actor": "michaelschnyder"
17
15
  },
18
16
  "runner": {
19
- "name": "GitHub Actions 1000001086",
17
+ "name": "GitHub Actions 1000001109",
20
18
  "os": "Linux",
21
19
  "architecture": "X64"
22
20
  },
23
21
  "workflow": {
24
- "runId": "33865033307",
25
- "runNumber": "7",
22
+ "runId": "33869547336",
23
+ "runNumber": "11",
26
24
  "runAttempt": "1",
27
- "url": "https://github.com/michaelschnyder/teams-cli/actions/runs/33865033307"
25
+ "url": "https://github.com/michaelschnyder/teams-cli/actions/runs/33869547336"
28
26
  },
29
27
  "releaseNotes": {
30
- "title": "Fix canary provenance race after rapid merges",
31
- "body": "## Summary\n\n- derive publication provenance from the commit actually checked out for the completed CI run\n- reject a canary when the checkout and workflow-run commit differ\n- defer commit-author lookup until the authoritative source commit is selected\n- cover the rapid-merge mismatch with a regression test\n\n## Verification\n\n- `node --check scripts/prepare-publication.mjs`\n- `node --import tsx --test test/release-metadata.test.ts`\n- `npm run check`\n- `CI=true npm test` (105 tests)\n- `npm run build`\n- `npm run package:check`\n- `npm run package:smoke`\n- GitHub CI on Ubuntu, macOS, and Windows; the Ubuntu dependency audit passed",
32
- "url": "https://github.com/michaelschnyder/teams-cli/pull/14"
28
+ "title": "Teams CLI v0.2.0",
29
+ "body": "## Highlights\n\n### Create workspace safeguards visually\n\n`teams-cli policy edit --open` now launches a local policy editor where you can choose which people, group chats, and channels may be read or posted to. You can review the generated policy, save a draft, and activate it when ready.\n\nRead and post access are configured independently, explicit denials take priority, and overlapping active policies can only narrow access. The editor keeps Teams credentials and message content out of the browser UI.\n\n### Get started with fewer identifiers and commands\n\n- `teams-cli login` signs in and discovers the default tenant and user automatically. Most users no longer need to provide tenant IDs, user IDs, or named profiles.\n- `teams-cli message send --person alice@example.com --body \"Hello\"` can find or start a direct conversation without requiring you to discover a chat ID first.\n- `teams-cli chat search \"Alice\" --json` uses bounded, server-ranked search instead of downloading your complete chat collection.\n\n### Set up coding agents more easily\n\nThe packaged guidance is now one consolidated `teams-cli` skill covering authentication, discovery, reading, messaging, and policies. `teams-cli skills install` installs or refreshes it for supported filesystem-based agents and migrates managed skills from version 0.1 automatically.\n\nClaude Desktop users also receive a versioned Cowork skill ZIP that can be uploaded through Cowork's skill settings. Interactive first-time login can offer agent-skill setup before authentication.\n\n### Diagnose local setup problems\n\nThe new `teams-cli doctor` command checks Node.js and browser availability, the selected identity, session health, managed skills, and detected agent environments without changing your configuration or contacting Teams. Use `teams-cli doctor --json` for structured output.\n\n### Try upcoming builds and stay updated\n\nThe CLI now supports stable and canary update channels, plus immutable branch snapshots for targeted testing. `teams-cli version` shows the installed channel, build provenance, release notes, and available updates. Globally installed stable and canary versions can update themselves with `teams-cli version --upgrade`; snapshot builds remain pinned until you explicitly switch channels.\n\n## Upgrade\n\nFor a global npm installation, run:\n\n```console\nteams-cli version --upgrade\n```\n\nYou can also install the release directly:\n\n```console\nnpm install --global @michaelschnyder/teams-cli@latest\n```\n\nIf you created a policy with version 0.1, open `teams-cli policy edit --open` and recreate it using the new people, group-chat, and channel read/post permissions. The previous `messageSend` policy shape is no longer accepted.\n\n> [!WARNING]\n> This project uses unsupported Microsoft Teams private APIs. Microsoft may change or block them without notice. Obtain organizational approval before use.\n",
30
+ "url": "https://github.com/michaelschnyder/teams-cli/releases/tag/v0.2.0"
33
31
  }
34
32
  }
package/dist/cli.js CHANGED
@@ -466,6 +466,7 @@ export function createProgram(options = {}) {
466
466
  .showHelpAfterError();
467
467
  const versionOptions = {
468
468
  ...(options.storageRoot ? { storageRoot: options.storageRoot } : {}),
469
+ confirm: interactiveAuth.confirm,
469
470
  ...(options.fetcher ? { fetcher: options.fetcher } : {}),
470
471
  ...(options.environment ? { environment: options.environment } : {}),
471
472
  };
@@ -2,7 +2,8 @@ import { Option } from "commander";
2
2
  import { stripVTControlCharacters } from "node:util";
3
3
  import { storagePaths } from "../storage.js";
4
4
  import { resolveUpdateChannel, saveUpdateChannel } from "../settings.js";
5
- import { checkForUpdate, isNpxExecution, updateChecksDisabled } from "../update.js";
5
+ import { checkForUpdate, isNpxExecution, latestForChannel, updateChecksDisabled } from "../update.js";
6
+ import { canUpgradeCli, upgradeCli } from "../upgrade.js";
6
7
  import { BUILD_INFO, CLI_VERSION, PACKAGE_NAME } from "../version.js";
7
8
  function safe(value) {
8
9
  return stripVTControlCharacters(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
@@ -47,7 +48,7 @@ export function renderVersion(info, channel, candidate, updateStatus = candidate
47
48
  lines.push(safe(candidate.summary.summary));
48
49
  if (candidate.summary?.url)
49
50
  lines.push(safe(candidate.summary.url));
50
- lines.push(`Update with the package manager and installation scope that installed the CLI, targeting ${PACKAGE_NAME}@${candidate.version}.`);
51
+ lines.push(`For a global npm installation, run \`teams-cli version --upgrade\`. Otherwise update through the package manager and installation scope that installed the CLI, targeting ${PACKAGE_NAME}@${candidate.version}.`);
51
52
  }
52
53
  else if (updateStatus === "current") {
53
54
  lines.push("", "No update is currently available.");
@@ -58,12 +59,13 @@ async function versionDetails(options) {
58
59
  const environment = options.environment ?? process.env;
59
60
  const stdout = options.stdout ?? process.stdout;
60
61
  const stderr = options.stderr ?? process.stderr;
62
+ const buildInfo = options.buildInfo ?? BUILD_INFO;
61
63
  const paths = storagePaths(options.storageRoot);
62
- const channel = await resolveUpdateChannel({ paths, environment, installedChannel: BUILD_INFO.channel });
64
+ const channel = await resolveUpdateChannel({ paths, environment, installedChannel: buildInfo.channel });
63
65
  const npx = isNpxExecution(environment);
64
66
  let candidate = null;
65
67
  let updateStatus = "current";
66
- if (BUILD_INFO.channel === "snapshot")
68
+ if (buildInfo.channel === "snapshot")
67
69
  updateStatus = "pinned";
68
70
  else if (npx)
69
71
  updateStatus = "npx";
@@ -71,7 +73,7 @@ async function versionDetails(options) {
71
73
  updateStatus = "disabled";
72
74
  else {
73
75
  try {
74
- candidate = await checkForUpdate(CLI_VERSION, channel, options.fetcher);
76
+ candidate = await checkForUpdate(buildInfo.version, channel, options.fetcher);
75
77
  updateStatus = candidate ? "available" : "current";
76
78
  }
77
79
  catch {
@@ -79,48 +81,76 @@ async function versionDetails(options) {
79
81
  }
80
82
  }
81
83
  if (options.json) {
82
- stdout.write(`${JSON.stringify({ installed: BUILD_INFO, updateChannel: channel, update: { status: updateStatus, candidate } }, null, 2)}\n`);
84
+ stdout.write(`${JSON.stringify({ installed: buildInfo, updateChannel: channel, update: { status: updateStatus, candidate } }, null, 2)}\n`);
83
85
  return;
84
86
  }
85
- stdout.write(renderVersion(BUILD_INFO, channel, candidate, updateStatus));
87
+ stdout.write(renderVersion(buildInfo, channel, candidate, updateStatus));
86
88
  if (updateStatus === "pinned")
87
89
  stderr.write("Snapshot builds are pinned. Replace this build explicitly through your package manager to leave the snapshot.\n");
88
90
  if (updateStatus === "npx")
89
91
  stderr.write("This temporary npx execution manages its version through the package spec, not the CLI updater.\n");
90
92
  if (updateStatus === "unavailable")
91
93
  stderr.write("The npm registry could not be checked; installed build information is still complete.\n");
94
+ if (candidate && options.allowPrompt && options.confirm && await (options.canUpgrade ?? canUpgradeCli)() &&
95
+ await options.confirm(`Install teams-cli ${candidate.version}?`)) {
96
+ stderr.write(`Upgrading teams-cli to ${candidate.version} through npm…\n`);
97
+ await (options.upgrader ?? upgradeCli)({ targetVersion: candidate.version });
98
+ stdout.write("teams-cli and recorded skill installations are up to date.\n");
99
+ }
92
100
  }
93
101
  async function changeChannel(channel, options) {
94
102
  const environment = options.environment ?? process.env;
95
103
  if (isNpxExecution(environment)) {
96
- throw new Error(`Cannot change persistent settings from npx. Run \`npx --prefer-online ${PACKAGE_NAME}@${channel === "stable" ? "latest" : "canary"} --version\` instead.`);
104
+ throw new Error(`Cannot switch an installation channel from npx. Run \`npx --prefer-online ${PACKAGE_NAME}@${channel === "stable" ? "latest" : "canary"} --version\` instead.`);
97
105
  }
98
106
  const paths = storagePaths(options.storageRoot);
99
- await saveUpdateChannel(paths, channel);
100
- const tag = channel === "stable" ? "latest" : "canary";
101
- (options.stdout ?? process.stdout).write(`teams-cli now checks the ${channel} channel. Update with the package manager and installation scope that installed the CLI, targeting ${PACKAGE_NAME}@${tag}.\n`);
102
- if (BUILD_INFO.channel === "snapshot") {
103
- (options.stderr ?? process.stderr).write("This installed snapshot remains pinned until you replace it through your package manager.\n");
107
+ const candidate = await latestForChannel(channel, options.fetcher);
108
+ (options.stderr ?? process.stderr).write(`Switching teams-cli to ${channel} (${candidate.version})…\n`);
109
+ await (options.upgrader ?? upgradeCli)({
110
+ targetVersion: candidate.version,
111
+ onInstalled: () => saveUpdateChannel(paths, channel),
112
+ });
113
+ (options.stdout ?? process.stdout).write(`teams-cli now follows the ${channel} channel at ${candidate.version}.\n`);
114
+ }
115
+ async function upgradeCurrent(options) {
116
+ const environment = options.environment ?? process.env;
117
+ if (isNpxExecution(environment)) {
118
+ throw new Error(`Cannot upgrade a temporary npx execution. Run \`npx --prefer-online ${PACKAGE_NAME}@latest --version\` instead.`);
119
+ }
120
+ const buildInfo = options.buildInfo ?? BUILD_INFO;
121
+ if (buildInfo.channel === "snapshot") {
122
+ throw new Error("Snapshot builds are pinned. Use `teams-cli version --channel stable` or `--channel canary` to leave this snapshot.");
104
123
  }
124
+ const paths = storagePaths(options.storageRoot);
125
+ const channel = await resolveUpdateChannel({ paths, environment, installedChannel: buildInfo.channel });
126
+ const candidate = await latestForChannel(channel, options.fetcher);
127
+ (options.stderr ?? process.stderr).write(`Upgrading teams-cli on ${channel} to ${candidate.version}…\n`);
128
+ await (options.upgrader ?? upgradeCli)({ targetVersion: candidate.version });
129
+ (options.stdout ?? process.stdout).write("teams-cli and recorded skill installations are up to date.\n");
105
130
  }
106
131
  export function registerVersionCommand(program, options = {}) {
107
132
  program.command("version")
108
- .description("Show build provenance or manage the update-notification channel")
109
- .addOption(new Option("--channel <channel>", "Select the update-notification channel").choices(["stable", "canary"]))
133
+ .description("Show build provenance, manage the update channel, or upgrade a global npm installation")
134
+ .option("--upgrade", "Install the newest version from the effective channel")
135
+ .addOption(new Option("--channel <channel>", "Switch a global npm installation channel").choices(["stable", "canary"]))
110
136
  .option("--json", "Output structured build and update information")
111
137
  .action(async (commandOptions) => {
112
- if (commandOptions.json && commandOptions.channel)
113
- throw new Error("--json cannot be combined with --channel");
138
+ if (commandOptions.channel && commandOptions.upgrade)
139
+ throw new Error("--channel already installs the newest version and cannot be combined with --upgrade");
140
+ if (commandOptions.json && (commandOptions.channel || commandOptions.upgrade))
141
+ throw new Error("--json cannot be combined with --channel or --upgrade");
114
142
  if (commandOptions.channel)
115
143
  return changeChannel(commandOptions.channel, options);
116
- return versionDetails({ ...options, json: commandOptions.json === true });
144
+ if (commandOptions.upgrade)
145
+ return upgradeCurrent(options);
146
+ return versionDetails({ ...options, json: commandOptions.json === true, allowPrompt: true });
117
147
  });
118
148
  }
119
149
  export async function showAdaptiveVersion(options = {}) {
120
150
  const stdout = options.stdout ?? process.stdout;
121
151
  if (!stdout.isTTY) {
122
- stdout.write(`${CLI_VERSION}\n`);
152
+ stdout.write(`${options.buildInfo?.version ?? CLI_VERSION}\n`);
123
153
  return;
124
154
  }
125
- await versionDetails(options);
155
+ await versionDetails({ ...options, allowPrompt: true });
126
156
  }
@@ -3,7 +3,7 @@ name: teams-cli
3
3
  description: Use teams-cli safely for Microsoft Teams authentication, discovery, reading, messaging, and policies.
4
4
  license: MIT
5
5
  metadata:
6
- version: "0.2.0-canary.7.1.g64541e1a"
6
+ version: "0.2.0"
7
7
  author: teams-cli
8
8
  ---
9
9
 
package/dist/update.js CHANGED
@@ -135,7 +135,7 @@ export async function prepareUpdateNotification(options) {
135
135
  if (state && state.channel === channel && state.pendingVersion && isNewerVersion(options.currentVersion, state.pendingVersion)) {
136
136
  const description = state.pendingSummary?.title ? ` ${state.pendingSummary.title}.` : "";
137
137
  (options.stderr ?? process.stderr).write(`A new teams-cli ${channel} version is available: ${options.currentVersion} → ${state.pendingVersion}.${description} ` +
138
- `Update through the package manager and installation scope that installed it, targeting ${PACKAGE_NAME}@${state.pendingVersion}.\n`);
138
+ `For a global npm installation, run \`teams-cli version --upgrade\`. Otherwise update through the package manager and installation scope that installed it, targeting ${PACKAGE_NAME}@${state.pendingVersion}.\n`);
139
139
  const consumed = { ...state };
140
140
  delete consumed.pendingVersion;
141
141
  delete consumed.pendingSummary;
@@ -0,0 +1,97 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { dirname, join, resolve, win32 } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { promisify } from "node:util";
6
+ import { PACKAGE_NAME } from "./version.js";
7
+ const execFileAsync = promisify(execFile);
8
+ const PACKAGE_ROOT = dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
9
+ export function npmInvocation(platform = process.platform, nodeExecutable = process.execPath, npmExecPath = process.env.npm_execpath) {
10
+ if (npmExecPath)
11
+ return { command: nodeExecutable, args: [npmExecPath] };
12
+ if (platform === "win32") {
13
+ return {
14
+ command: nodeExecutable,
15
+ args: [win32.join(win32.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js")],
16
+ };
17
+ }
18
+ return { command: "npm", args: [] };
19
+ }
20
+ async function defaultGlobalRoot(npm) {
21
+ const { stdout } = await execFileAsync(npm.command, [...npm.args, "root", "--global"], {
22
+ encoding: "utf8",
23
+ windowsHide: true,
24
+ });
25
+ const root = stdout.trim();
26
+ if (!root)
27
+ throw new Error("npm returned no global package root");
28
+ return root;
29
+ }
30
+ function comparablePath(path, platform) {
31
+ const absolute = resolve(path);
32
+ return platform === "win32" ? absolute.toLowerCase() : absolute;
33
+ }
34
+ export async function resolveGlobalNpmInstallation(options = {}) {
35
+ const platform = options.platform ?? process.platform;
36
+ const npm = options.npm ?? npmInvocation(platform);
37
+ let globalRoot;
38
+ try {
39
+ globalRoot = await (options.globalRoot ?? defaultGlobalRoot)(npm);
40
+ }
41
+ catch (error) {
42
+ const detail = error instanceof Error ? `: ${error.message}` : "";
43
+ throw new Error(`Cannot verify the npm global installation scope${detail}`);
44
+ }
45
+ const packageRoot = options.packageRoot ?? PACKAGE_ROOT;
46
+ const expectedRoot = join(globalRoot, ...PACKAGE_NAME.split("/"));
47
+ const canonicalize = options.canonicalize ?? realpathSync;
48
+ let actual;
49
+ let expected;
50
+ try {
51
+ actual = canonicalize(packageRoot);
52
+ expected = canonicalize(expectedRoot);
53
+ }
54
+ catch {
55
+ throw new Error("Cannot verify that this teams-cli package belongs to npm's global installation scope");
56
+ }
57
+ if (comparablePath(actual, platform) !== comparablePath(expected, platform)) {
58
+ throw new Error("Automatic upgrade is available only for a global npm installation managed by the active npm executable. " +
59
+ "Update this package through the package manager and installation scope that installed it.");
60
+ }
61
+ return {
62
+ npm,
63
+ globalRoot,
64
+ packageRoot: actual,
65
+ installedCli: join(expectedRoot, "dist", "cli.js"),
66
+ };
67
+ }
68
+ export async function canUpgradeCli(options = {}) {
69
+ try {
70
+ await resolveGlobalNpmInstallation(options);
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ const defaultRunner = (command, args) => new Promise((resolveRun, reject) => {
78
+ const child = spawn(command, [...args], { stdio: "inherit", shell: false, windowsHide: true });
79
+ child.once("error", reject);
80
+ child.once("exit", (code) => resolveRun(code ?? 1));
81
+ });
82
+ export async function upgradeCli(options = {}) {
83
+ const installation = await resolveGlobalNpmInstallation(options);
84
+ const runner = options.runner ?? defaultRunner;
85
+ const targetVersion = options.targetVersion ?? "latest";
86
+ const installStatus = await runner(installation.npm.command, [...installation.npm.args, "install", "--global", `${PACKAGE_NAME}@${targetVersion}`]);
87
+ if (installStatus !== 0)
88
+ throw new Error(`npm upgrade failed with exit code ${installStatus}`);
89
+ if (!(options.pathExists ?? existsSync)(installation.installedCli)) {
90
+ throw new Error(`The updated CLI was not found at ${installation.installedCli}`);
91
+ }
92
+ await options.onInstalled?.();
93
+ const reinstallStatus = await runner(process.execPath, [installation.installedCli, "skills", "reinstall"]);
94
+ if (reinstallStatus !== 0) {
95
+ throw new Error(`The CLI was upgraded, but skill reinstallation failed with exit code ${reinstallStatus}`);
96
+ }
97
+ }
package/docs/releasing.md CHANGED
@@ -37,7 +37,7 @@ Published npm versions are immutable. If a release is defective, fix it in a new
37
37
 
38
38
  ## Canary releases
39
39
 
40
- Merging a pull request into `main` publishes one prerelease to the `canary` npm tag after the resulting main-branch CI run passes on Ubuntu, macOS, and Windows. The publishing workflow consumes that successful CI result instead of repeating the operating-system matrix. A direct commit to `main` does not publish. Stable releases and manually requested snapshots run package verification on Ubuntu before publishing. The workflow uses the package version when it is already greater than npm's latest stable release; otherwise it assumes the next patch. Versions include the workflow run, attempt, and source commit, while the package embeds the pull request notes and build provenance.
40
+ Merging a pull request into `main` publishes one prerelease to the `canary` npm tag after the resulting main-branch CI run passes on Ubuntu, macOS, and Windows. The publishing workflow consumes that successful CI result instead of repeating the operating-system matrix. A direct commit to `main` does not publish. Stable releases run package verification on Ubuntu without repeating the dependency audit that already gates `main`; manually requested snapshots include the audit because their selected branch might not have passed `main` CI. The workflow uses the package version when it is already greater than npm's latest stable release; otherwise it assumes the next patch. Versions include the workflow run, attempt, and source commit, while the package embeds the pull request notes and build provenance.
41
41
 
42
42
  Install or follow this channel with:
43
43
 
@@ -46,8 +46,10 @@ npm install --global @michaelschnyder/teams-cli@canary
46
46
  teams-cli version --channel canary
47
47
  ```
48
48
 
49
+ The npm command is useful when installing canary for the first time or upgrading from a release that predates channel switching. Once the channel-aware CLI is installed globally through npm, `version --channel canary` installs the newest canary and records the selection.
50
+
49
51
  ## Branch snapshots
50
52
 
51
53
  Repository contributors with write, maintain, or admin permission can run the publish workflow from `main` and enter a repository branch to snapshot. Manual runs are automatically classified as snapshot publications. The workflow verifies that the requested branch and checked-out commit belong to this repository; fork refs and dispatches using a modified branch workflow are not accepted. It publishes an immutable `snapshot` prerelease, associates an npm tag with the selected branch, and prints exact global-install and npx commands in its summary. Canary summaries provide both commands as well.
52
54
 
53
- Snapshots never update automatically. Testers leave a snapshot by explicitly installing `latest` or `canary` through their package manager and selecting the corresponding notification channel.
55
+ Snapshots never update automatically and `version --upgrade` refuses to replace them. Testers with a global npm installation can leave a snapshot explicitly with `version --channel stable` or `version --channel canary`; other installation scopes must use their owning package manager.
@@ -8,9 +8,11 @@ The default session created by `teams-cli login` or `teams-cli auth login` is us
8
8
  teams-cli --version
9
9
  teams-cli version
10
10
  teams-cli version --json
11
+ teams-cli version --upgrade
12
+ teams-cli version --channel canary
11
13
  ```
12
14
 
13
- When piped, `--version` prints only semantic version text. In a terminal, version output includes the installed channel, local build provenance, associated release notes, and any available update. The policy editor shows the same exact version in its footer.
15
+ When piped, `--version` prints only semantic version text. In a terminal, version output includes the installed channel, local build provenance, associated release notes, and any available update. A verified global npm installation can accept the interactive update offer or use `version --upgrade`; npx, snapshots, project dependencies, and installations owned by another package manager are not changed automatically. Use an explicit `--channel stable|canary` to replace a global npm installation and persist that channel after installation succeeds. The policy editor shows the same exact version in its footer.
14
16
 
15
17
  ## Diagnose the local setup
16
18
 
@@ -80,20 +80,18 @@ At startup, the CLI may launch a detached npm registry check. It runs at most on
80
80
  Stable installations follow npm's `latest` tag. Switch to builds produced from newly merged pull requests with:
81
81
 
82
82
  ```bash
83
- npm install --global @michaelschnyder/teams-cli@canary
84
83
  teams-cli version --channel canary
85
84
  ```
86
85
 
87
- The npm command replaces the globally installed package, while the CLI command records which channel should be checked for update notifications in `~/.teams-cli/settings.yaml`. Switch back in the same way:
86
+ For a global npm installation, this installs the newest canary and records the channel in `~/.teams-cli/settings.yaml` only after installation succeeds. Switch back in the same way:
88
87
 
89
88
  ```bash
90
- npm install --global @michaelschnyder/teams-cli@latest
91
89
  teams-cli version --channel stable
92
90
  ```
93
91
 
94
92
  `TEAMS_CLI_UPDATE_CHANNEL=stable|canary` overrides the recorded channel for the current process. Channel settings are user-wide and deliberately separate from Teams profiles and workspace policies.
95
93
 
96
- Contributor snapshots are installed using the exact version shown by their GitHub Actions run. Snapshots are pinned and do not perform background update checks. Replace a snapshot explicitly through the same package manager and installation scope that installed it, then select `stable` or `canary` for future notifications.
94
+ Contributor snapshots are installed using the exact version shown by their GitHub Actions run. Snapshots are pinned: they do not perform background update checks and `version --upgrade` refuses to replace them. A globally installed snapshot can leave the pinned build through an explicit `teams-cli version --channel stable` or `--channel canary` command.
97
95
 
98
96
  Disable checks with either environment variable:
99
97
 
@@ -103,11 +101,15 @@ export NO_UPDATE_NOTIFIER=1
103
101
  export TEAMS_CLI_DISABLE_UPDATE_CHECK=1
104
102
  ```
105
103
 
106
- Checks are automatically disabled in CI and temporary npx executions. Inspect local build provenance and any associated release notes with `teams-cli version`, or use `teams-cli version --json` for structured output.
104
+ Checks are automatically disabled in CI and temporary npx executions. Inspect local build provenance and any associated release notes with `teams-cli version`, or use `teams-cli version --json` for structured output. An interactive version check offers to install an available update when the running package is verified as a global npm installation. Upgrade explicitly within the effective stable or canary channel with:
107
105
 
108
- The CLI does not run npm or change global or project dependencies itself, because it cannot reliably determine the package manager and installation scope that installed it. Update notices provide an exact package target. Use `npm install --global` only for an existing global npm installation; for a project dependency, update it through that project's package manager instead. Refresh recorded agent-skill copies separately with `teams-cli skills reinstall` after replacing the package.
106
+ ```bash
107
+ teams-cli version --upgrade
108
+ ```
109
+
110
+ Before making changes, the CLI asks the active npm executable for its global package root and verifies that the running package is the `teams-cli` package at that exact location. It then installs the registry's exact selected version without a command shell and refreshes managed agent-skill copies through the newly installed CLI. This prevents a project dependency, another package manager's installation, or a source checkout from being converted into a global npm package. Those installations receive an exact package target and must be updated through their owning package manager and scope.
109
111
 
110
- An npx execution cannot change the persistent notification channel. Request the desired package explicitly instead:
112
+ An npx execution cannot upgrade itself or change the persistent channel. Request the desired package explicitly instead:
111
113
 
112
114
  ```bash
113
115
  npx --prefer-online @michaelschnyder/teams-cli@latest --help
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michaelschnyder/teams-cli",
3
- "version": "0.2.0-canary.7.1.g64541e1a",
3
+ "version": "0.2.0",
4
4
  "description": "A safety-conscious CLI for persistent Microsoft Teams sessions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -66,11 +66,11 @@
66
66
  "node": ">=22.20.0"
67
67
  },
68
68
  "teamsCli": {
69
- "channel": "canary",
69
+ "channel": "stable",
70
70
  "releaseSummary": {
71
- "title": "Fix canary provenance race after rapid merges",
72
- "summary": "## Summary",
73
- "url": "https://github.com/michaelschnyder/teams-cli/pull/14"
71
+ "title": "Teams CLI v0.2.0",
72
+ "summary": "## Highlights",
73
+ "url": "https://github.com/michaelschnyder/teams-cli/releases/tag/v0.2.0"
74
74
  }
75
75
  }
76
76
  }