@skillcdn/console-plugin-messenger 0.0.0 → 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 CHANGED
@@ -11,7 +11,7 @@ The contract is `docs/specs/plugins.md` of the console's repository, shipped in
11
11
  | Connect Slack: the app made from the plugin's manifest, its client and signing secrets sealed by the console from Slack's answer; or the app brought up to date | the messenger role, on the Connection page or with `connect` | the command `connect`: `apps.manifest.create` with the configuration token, or `apps.manifest.update`; the client id kept as a plain field, since Slack puts it in addresses; an app whose secrets were not sealed taken away again | `connection.test.ts`, "connect" |
12
12
  | Install the app in the workspace | someone who may install apps there, pressing Install | Slack's page opened from the press (`frame.open`); the endpoint `slack-oauth` (a person's, the role `messenger`): the state the installation was started with checked and spent, the code exchanged with the client secret in the body, the bot's token sealed, the browser sent back to the Connection page | "the installation's return" |
13
13
  | See how the connection stands | the messenger role | the query `status`: the console's address and whether Slack can reach it, each secret as the console says it, the app, HR and the role asked of it | "status" |
14
- | Send a test; uninstall | the messenger role | the commands `test` (a direct message to the caller, or a channel) and `disconnect` (`apps.uninstall`) | "test and disconnect" |
14
+ | Send a test; disconnect, to start again in this workspace or another | the messenger role | the commands `test` (a direct message to the caller, or a channel) and `disconnect`: `apps.uninstall` where the app is installed, then `apps.manifest.delete`, the app forgotten even where the token pasted since cannot delete it (the answer says to delete it on Slack's page), and its threads archived, since a new app's bot is in none of their conversations; the next installation, in a workspace other than the last, archives the last one's routes and accounts and has its people matched within the hour | "test and disconnect"; "the installation's return" |
15
15
  | Keep the configuration token and the app's manifest current | the console, every hour | the job `keep`: the configuration token renewed with its refresh token when its age is unknown or less than four hours are left, both sealed again for twelve hours; the manifest written where its digest differs from the one wanted | "the hourly job and HR's lifecycle" |
16
16
  | The leave shortcut, present while HR is | the console, when HR is enabled, disabled or upgraded | the hooks `console:plugin.enabled`, `console:plugin.disabled`, `console:plugin.upgraded`: the manifest written again, with the shortcut while HR is on in a version this plugin reads | "the hourly job and HR's lifecycle" |
17
17
  | Match people to their Slack accounts by email | the console, within the hour of connecting, then every day; the messenger role, with `match` | the job `match`, the command `match`: the directory against `users.list`, two hundred a page, bots and the deleted left out; one person by `users.lookupByEmail` on the hooks `console:person.email_changed`, `console:person.reactivated` and `console:person.deactivated` | `matching.test.ts` |
@@ -19986,7 +19986,7 @@ function date4(params) {
19986
19986
  return _coercedDate(ZodDate, params);
19987
19987
  }
19988
19988
 
19989
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/definition-error.js
19989
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/definition-error.js
19990
19990
  var PluginDefinitionError = class extends Error {
19991
19991
  problems;
19992
19992
  constructor(problems) {
@@ -19997,7 +19997,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19997
19997
  }
19998
19998
  };
19999
19999
 
20000
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/limits.js
20000
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/limits.js
20001
20001
  var MAX_PLUGIN_NAME_LENGTH = 40;
20002
20002
  var MAX_PLUGIN_DESCRIPTION_LENGTH = 200;
20003
20003
  var MAX_PLUGIN_AGENT_NOTE_LENGTH = 200;
@@ -20245,14 +20245,14 @@ function superRefine2(fn, params) {
20245
20245
  return _superRefine(fn, params);
20246
20246
  }
20247
20247
 
20248
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/limits.js
20248
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/limits.js
20249
20249
  var MAX_FILE_BYTES = 10 * 1024 * 1024;
20250
20250
  var MAX_PROJECT_KEY_LENGTH = 40;
20251
20251
  var MAX_PROJECT_NAME_LENGTH = 100;
20252
20252
  var MAX_PROJECT_DESCRIPTION_LENGTH = 500;
20253
20253
  var MAX_PROJECT_ICON_BYTES = 512 * 1024;
20254
20254
 
20255
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/text.js
20255
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/text.js
20256
20256
  function isForbidden(codePoint, multiline) {
20257
20257
  if (codePoint <= 31) {
20258
20258
  return !(multiline && (codePoint === 9 || codePoint === 10 || codePoint === 13));
@@ -20271,10 +20271,10 @@ function hasForbiddenCodePoint(value, multiline = false) {
20271
20271
  return false;
20272
20272
  }
20273
20273
 
20274
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/bridge.js
20274
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/bridge.js
20275
20275
  var PLUGIN_WIDGET_SIZE_NAMES = ["small", "medium", "large", "wide", "full"];
20276
20276
 
20277
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/capabilities.js
20277
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/capabilities.js
20278
20278
  var PLUGIN_CAPABILITIES = [
20279
20279
  "files",
20280
20280
  "people:read",
@@ -20309,7 +20309,7 @@ function isPluginCapability(value) {
20309
20309
  return false;
20310
20310
  }
20311
20311
 
20312
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/manifest.js
20312
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/manifest.js
20313
20313
  var PLUGIN_KEY_PATTERN = /^[a-z][a-z0-9-]{1,31}$/;
20314
20314
  var CURRENT_PLUGIN_API = 1;
20315
20315
  var RESERVED_PLUGIN_KEYS = [
@@ -20747,7 +20747,7 @@ function manifestProblems(manifest) {
20747
20747
  return problems;
20748
20748
  }
20749
20749
 
20750
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/args.js
20750
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/args.js
20751
20751
  function common(options) {
20752
20752
  const about = options?.about;
20753
20753
  if (about !== void 0 && (about.length === 0 || about.length > MAX_PLUGIN_ABOUT_LENGTH || /[\r\n]/.test(about))) {
@@ -20864,7 +20864,7 @@ var arg = {
20864
20864
  }
20865
20865
  };
20866
20866
 
20867
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/define.js
20867
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/define.js
20868
20868
  var FIELD = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
20869
20869
  var HOOK_KIND = /^(?:console:[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)*|plugin:[a-z][a-z0-9-]{1,31}:[a-z][a-z0-9-]{1,31})$/;
20870
20870
  var TIME_OF_DAY2 = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
@@ -21164,7 +21164,7 @@ function definePlugin(parts) {
21164
21164
  });
21165
21165
  }
21166
21166
 
21167
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/refusals.js
21167
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/refusals.js
21168
21168
  var PLUGIN_REFUSALS = {
21169
21169
  "plugin.not_found": 404,
21170
21170
  "plugin.disabled": 409,
@@ -21210,7 +21210,7 @@ function isPluginRefusal(value) {
21210
21210
  return value instanceof PluginRefusal || typeof value === "object" && value !== null && value.name === "PluginRefusal" && typeof value.code === "string";
21211
21211
  }
21212
21212
 
21213
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/semver.js
21213
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/semver.js
21214
21214
  function parseLooseVersion(text) {
21215
21215
  const match3 = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-+].*)?$/.exec(text.trim());
21216
21216
  return match3 === null ? void 0 : [Number(match3[1]), Number(match3[2] ?? "0"), Number(match3[3] ?? "0")];
@@ -21278,7 +21278,8 @@ var EN = {
21278
21278
  appMade: (url3) => `The Slack app is made. Someone who may install apps in the workspace opens this address to install it: ${url3}`,
21279
21279
  appUpdated: (url3) => `The Slack app is up to date. To install it in the workspace, open this address: ${url3}`,
21280
21280
  appInstalledUpdated: "The Slack app is up to date and installed.",
21281
- disconnected: "The app is uninstalled from the workspace and its token revoked; connect installs it again.",
21281
+ disconnected: "The app is uninstalled from the workspace and deleted. connect makes a new one, in this workspace or another.",
21282
+ disconnectedAppLeft: "The app is no longer used, but the configuration token could not delete it in Slack: delete it at api.slack.com/apps. connect makes a new one.",
21282
21283
  testSent: (where) => `A test message went to ${where}.`,
21283
21284
  matched: (matched, unmatched) => `Matched ${matched} people to Slack accounts by email; ${unmatched} have none.`,
21284
21285
  matchedOne: (name) => `${name} is matched to a Slack account.`,
@@ -21389,7 +21390,8 @@ var KO = {
21389
21390
  appMade: (url3) => `Slack \uC571\uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4. \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uC571\uC744 \uC124\uCE58\uD560 \uC218 \uC788\uB294 \uC0AC\uB78C\uC774 \uC774 \uC8FC\uC18C\uB97C \uC5F4\uC5B4 \uC124\uCE58\uD558\uC138\uC694: ${url3}`,
21390
21391
  appUpdated: (url3) => `Slack \uC571\uC774 \uCD5C\uC2E0\uC785\uB2C8\uB2E4. \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uC124\uCE58\uD558\uB824\uBA74 \uC774 \uC8FC\uC18C\uB97C \uC5EC\uC138\uC694: ${url3}`,
21391
21392
  appInstalledUpdated: "Slack \uC571\uC774 \uCD5C\uC2E0\uC774\uACE0 \uC124\uCE58\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.",
21392
- disconnected: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uC571\uC744 \uC81C\uAC70\uD558\uACE0 \uD1A0\uD070\uC744 \uD3D0\uAE30\uD588\uC2B5\uB2C8\uB2E4. connect\uB85C \uB2E4\uC2DC \uC124\uCE58\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
21393
+ disconnected: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uC571\uC744 \uC81C\uAC70\uD558\uACE0 \uC9C0\uC6E0\uC2B5\uB2C8\uB2E4. connect\uB85C \uC0C8 \uC571\uC744 \uB9CC\uB4E4 \uC218 \uC788\uACE0, \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uB3C4 \uC5F0\uACB0\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
21394
+ disconnectedAppLeft: "\uC774\uC81C \uC774 \uC571\uC744 \uC4F0\uC9C0 \uC54A\uC9C0\uB9CC, \uC124\uC815 \uD1A0\uD070\uC73C\uB85C Slack\uC5D0\uC11C \uC9C0\uC6B0\uC9C0\uB294 \uBABB\uD588\uC2B5\uB2C8\uB2E4. api.slack.com/apps\uC5D0\uC11C \uC9C0\uC6B0\uC138\uC694. connect\uB85C \uC0C8 \uC571\uC744 \uB9CC\uB4E4 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
21393
21395
  testSent: (where) => `${where}\uC5D0 \uC2DC\uD5D8 \uBA54\uC2DC\uC9C0\uB97C \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.`,
21394
21396
  matched: (matched, unmatched) => `\uC774\uBA54\uC77C\uB85C ${matched}\uBA85\uC744 Slack \uACC4\uC815\uACFC \uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4. ${unmatched}\uBA85\uC740 \uACC4\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`,
21395
21397
  matchedOne: (name) => `${name} \uB2D8\uC744 Slack \uACC4\uC815\uACFC \uC5F0\uACB0\uD588\uC2B5\uB2C8\uB2E4.`,
@@ -22085,6 +22087,21 @@ async function noteProblem(ctx, problem) {
22085
22087
  problemAt: problem === null ? null : ctx.now().toISOString()
22086
22088
  });
22087
22089
  }
22090
+ async function archiveAll(ctx, collection) {
22091
+ const records = ctx.records(collection);
22092
+ let archived = 0;
22093
+ for (let round = 0; round < 50; round += 1) {
22094
+ const page = await records.list({ limit: 100 });
22095
+ if (page.items.length === 0) {
22096
+ break;
22097
+ }
22098
+ for (const record3 of page.items) {
22099
+ await records.archive(record3.id);
22100
+ archived += 1;
22101
+ }
22102
+ }
22103
+ return archived;
22104
+ }
22088
22105
  function isConnected(connection) {
22089
22106
  return connection !== null && connection.data.phase === "connected" && connection.data.teamId !== null;
22090
22107
  }
@@ -22344,16 +22361,16 @@ var connect = defineCommand({
22344
22361
  }
22345
22362
  });
22346
22363
  var disconnect = defineCommand({
22347
- about: "Uninstalls the app from the Slack workspace and revokes its token.",
22364
+ about: "Uninstalls the app from the Slack workspace and deletes it; connect then makes a new one.",
22348
22365
  roles: [ROLE],
22349
22366
  async run(ctx) {
22350
22367
  requireRole(ctx);
22351
22368
  const words = serverWords(ctx.language);
22352
22369
  const connection = await connectionOf(ctx);
22353
- if (connection === null || connection.data.clientId === null) {
22370
+ if (connection === null || connection.data.appId === null) {
22354
22371
  throw new PluginError("not_made", words.refused.notMade);
22355
22372
  }
22356
- if (isConnected(connection)) {
22373
+ if (isConnected(connection) && connection.data.clientId !== null) {
22357
22374
  try {
22358
22375
  await slackCall(
22359
22376
  ctx,
@@ -22367,15 +22384,36 @@ var disconnect = defineCommand({
22367
22384
  }
22368
22385
  }
22369
22386
  }
22387
+ const appId = connection.data.appId;
22388
+ let deleted = true;
22389
+ try {
22390
+ await withConfigToken(
22391
+ ctx,
22392
+ () => slack(ctx, "apps.manifest.delete", { app_id: appId }, "config")
22393
+ );
22394
+ } catch (error62) {
22395
+ deleted = false;
22396
+ ctx.log.warn("the app was not deleted", {
22397
+ code: error62 instanceof SlackError ? error62.code : refusalCode(error62)
22398
+ });
22399
+ }
22400
+ await archiveAll(ctx, threads);
22370
22401
  await saveConnection(ctx, {
22371
22402
  phase: "disconnected",
22403
+ appId: null,
22404
+ clientId: null,
22372
22405
  botUserId: null,
22406
+ manifestDigest: null,
22407
+ manifestAt: null,
22373
22408
  oauthState: null,
22374
22409
  oauthUntil: null,
22375
22410
  problem: null,
22376
22411
  problemAt: null
22377
22412
  });
22378
- return { text: words.said.disconnected, data: { phase: "disconnected" } };
22413
+ return {
22414
+ text: deleted ? words.said.disconnected : words.said.disconnectedAppLeft,
22415
+ data: { phase: "disconnected", deleted }
22416
+ };
22379
22417
  }
22380
22418
  });
22381
22419
  var test = defineCommand({
@@ -23528,9 +23566,18 @@ var slackOauthEndpoint = defineEndpoint({
23528
23566
  await noteProblem(ctx, words.sealFailed("the bot's token"));
23529
23567
  return back(ctx);
23530
23568
  }
23569
+ const teamId = textOf(answer.body, "team.id");
23570
+ const before = connection.data.teamId;
23571
+ const elsewhere = before !== null && teamId !== null && before !== teamId;
23572
+ if (elsewhere) {
23573
+ await archiveAll(ctx, routes);
23574
+ await archiveAll(ctx, links);
23575
+ await archiveAll(ctx, threads);
23576
+ }
23531
23577
  await saveConnection(ctx, {
23578
+ ...elsewhere ? { matchedAt: null, matched: 0, unmatched: 0 } : {},
23532
23579
  phase: "connected",
23533
- teamId: textOf(answer.body, "team.id"),
23580
+ teamId,
23534
23581
  teamName: textOf(answer.body, "team.name"),
23535
23582
  botUserId: textOf(answer.body, "bot_user_id"),
23536
23583
  connectedAt: ctx.now().toISOString(),
package/dist/web/index.js CHANGED
@@ -1,7 +1,7 @@
1
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/vocabulary.js
1
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/vocabulary.js
2
2
  var WEEK_STARTS = ["auto", "monday", "sunday"];
3
3
 
4
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/bridge.js
4
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/bridge.js
5
5
  var PLUGIN_BRIDGE_VERSION = 1;
6
6
  var PLUGIN_WIDGET_SIZE_NAMES = ["small", "medium", "large", "wide", "full"];
7
7
  var BRIDGE_METHODS = [
@@ -74,7 +74,7 @@ function isBridgeToFrame(value) {
74
74
  }
75
75
  }
76
76
 
77
- // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@ty_cea9aebb2d21d482714a27fcf2e4373a/node_modules/@skillcdn/console/dist/plugin/web.js
77
+ // ../../node_modules/.pnpm/@skillcdn+console@0.4.6_@types+react@19.3.0_react-dom@19.3.1_react@19.3.1__react@19.3.1/node_modules/@skillcdn/console/dist/plugin/web.js
78
78
  var PluginBridgeError = class extends Error {
79
79
  code;
80
80
  data;
@@ -963,20 +963,29 @@ function build(frame, words, status) {
963
963
  );
964
964
  }
965
965
  parts.push(panel(w.hr, ...hrParts));
966
- if (connected) {
967
- const channel = input("text", { placeholder: "C0123ABCD", maxLength: 40 });
968
- const testStatus = statusLine();
969
- const send = button(w.test, () => {
970
- const value = channel.value.trim();
971
- void runCommand(
972
- frame,
973
- send,
966
+ if (connection !== null && connection.appId !== null) {
967
+ const tryParts = [];
968
+ if (connected) {
969
+ const channel = input("text", { placeholder: "C0123ABCD", maxLength: 40 });
970
+ const testStatus = statusLine();
971
+ const send = button(w.test, () => {
972
+ const value = channel.value.trim();
973
+ void runCommand(
974
+ frame,
975
+ send,
976
+ testStatus,
977
+ words,
978
+ "test",
979
+ value.length > 0 ? { channel: value } : {}
980
+ );
981
+ });
982
+ tryParts.push(
983
+ field(w.testChannel, channel, w.testChannelHint),
984
+ toolbar(send),
974
985
  testStatus,
975
- words,
976
- "test",
977
- value.length > 0 ? { channel: value } : {}
986
+ el("hr", "rule")
978
987
  );
979
- });
988
+ }
980
989
  const outStatus = statusLine();
981
990
  let armed = false;
982
991
  const out = button(
@@ -993,11 +1002,8 @@ function build(frame, words, status) {
993
1002
  );
994
1003
  parts.push(
995
1004
  panel(
996
- w.tryIt,
997
- field(w.testChannel, channel, w.testChannelHint),
998
- toolbar(send),
999
- testStatus,
1000
- el("hr", "rule"),
1005
+ connected ? w.tryIt : w.disconnect,
1006
+ ...tryParts,
1001
1007
  muted(w.disconnectHint),
1002
1008
  toolbar(out),
1003
1009
  outStatus
@@ -1326,8 +1332,8 @@ var EN = {
1326
1332
  testChannelHint: "A channel's id (C0123ABCD); left empty, a direct message to you.",
1327
1333
  test: "Send a test message",
1328
1334
  disconnect: "Disconnect",
1329
- disconnectSure: "Press again to uninstall",
1330
- disconnectHint: "Uninstalls the app from the workspace and revokes its token; installing it again brings it back.",
1335
+ disconnectSure: "Press again to uninstall and delete",
1336
+ disconnectHint: "Uninstalls the app from the workspace and deletes it. Connecting again makes a new app: in this workspace, the routes and the matched accounts stay; in another, they go, and its people are matched within the hour.",
1331
1337
  failures: (count) => `${count} deliveries failed in the last day; the Activity page says why.`
1332
1338
  },
1333
1339
  routes: {
@@ -1518,8 +1524,8 @@ var KO = {
1518
1524
  testChannelHint: "\uCC44\uB110 ID(C0123ABCD). \uBE44\uC6CC \uB450\uBA74 \uB098\uC5D0\uAC8C DM\uC73C\uB85C \uBCF4\uB0C5\uB2C8\uB2E4.",
1519
1525
  test: "\uC2DC\uD5D8 \uBA54\uC2DC\uC9C0 \uBCF4\uB0B4\uAE30",
1520
1526
  disconnect: "\uC5F0\uACB0 \uD574\uC81C",
1521
- disconnectSure: "\uD55C \uBC88 \uB354 \uB204\uB974\uBA74 \uC81C\uAC70\uD569\uB2C8\uB2E4",
1522
- disconnectHint: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uC571\uC744 \uC81C\uAC70\uD558\uACE0 \uD1A0\uD070\uC744 \uD3D0\uAE30\uD569\uB2C8\uB2E4. \uB2E4\uC2DC \uC124\uCE58\uD558\uBA74 \uB3CC\uC544\uC635\uB2C8\uB2E4.",
1527
+ disconnectSure: "\uD55C \uBC88 \uB354 \uB204\uB974\uBA74 \uC81C\uAC70\uD558\uACE0 \uC9C0\uC6C1\uB2C8\uB2E4",
1528
+ disconnectHint: "\uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uC11C \uC571\uC744 \uC81C\uAC70\uD558\uACE0 \uC9C0\uC6C1\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uBA74 \uC0C8 \uC571\uC744 \uB9CC\uB4ED\uB2C8\uB2E4. \uAC19\uC740 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uBA74 \uCC44\uB110 \uC54C\uB9BC\uACFC \uACC4\uC815 \uC5F0\uACB0\uC774 \uADF8\uB300\uB85C \uB0A8\uACE0, \uB2E4\uB978 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uBA74 \uC9C0\uC6CC\uC9C0\uBA70 \uADF8\uACF3 \uC0AC\uB78C\uB4E4\uC758 \uACC4\uC815\uC740 \uD55C \uC2DC\uAC04 \uC548\uC5D0 \uB2E4\uC2DC \uC5F0\uACB0\uB429\uB2C8\uB2E4.",
1523
1529
  failures: (count) => `\uC9C0\uB09C \uD558\uB8E8 \uB3D9\uC548 ${count}\uAC74\uC744 \uBCF4\uB0B4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC804\uC1A1 \uAE30\uB85D\uC5D0\uC11C \uC774\uC720\uB97C \uBCF4\uC138\uC694.`
1524
1530
  },
1525
1531
  routes: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skillcdn/console-plugin-messenger",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Messenger, a plugin of the SkillCDN Console: every message a plugin has for people, carried to the messengers an organization uses and back, Slack first; for HR, a leave form in the chat and the approval threaded with whoever decides.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,7 +21,7 @@ The messenger carries what the console's plugins have for people to the organiza
21
21
  | See how the connection stands: the console's address, the secrets, the app, HR, the role asked of HR | `console plugin messenger status` |
22
22
  | Make the Slack app, or bring it up to date, and get the address that installs it | `console plugin messenger connect [--language en|ko]` |
23
23
  | Send a test message to yourself, or to a channel | `console plugin messenger test [--channel <id>]` |
24
- | Uninstall the app from the workspace | `console plugin messenger disconnect` |
24
+ | Uninstall the app from the workspace and delete it, to start again in this workspace or another | `console plugin messenger disconnect` |
25
25
  | Match people to their Slack accounts by email now: everyone, or one person | `console plugin messenger match [--person <login>]` |
26
26
  | See who is matched, and why the others are not | `console plugin messenger people [--state no_email|no_account|not_checked|matched] [--limit <n>]` |
27
27
  | List the channels a route may name | `console plugin messenger channels [--search <words>]` |
@@ -40,6 +40,8 @@ The messenger carries what the console's plugins have for people to the organiza
40
40
  6. **HR.** For the leave shortcut and the threads, an administrator turns on `plugins:hr` among the plugin's optional capabilities and grants it HR's role `hr` on the plugin's settings page; `status` says what is missing.
41
41
  7. **The routes.** `channels` lists the channels; `route <channel>` sends approved leave there. A private channel takes posts only once someone invites the app to it in Slack.
42
42
 
43
+ To move to another workspace, as from a workspace kept for trying the plugin to the organization's own: `disconnect`, paste the other workspace's configuration tokens, then `connect` and install there. The routes and the matched accounts of the last workspace go when the new one is installed; in the same workspace they stay.
44
+
43
45
  ## When a message did not go out
44
46
 
45
47
  - `activity --outcome failed` says what Slack or the console answered, and `activity --outcome skipped` why an event was passed over: Slack not connected, HR's api off, the role not granted, nobody in Slack to tell.