@doist/twist-cli 2.9.1 → 2.10.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
@@ -26,12 +26,41 @@ This makes the `tw` command available globally.
26
26
 
27
27
  ## Setup
28
28
 
29
- Set up your Twist API token:
30
-
31
29
  ```bash
32
30
  tw auth login
33
31
  ```
34
32
 
33
+ This opens your browser to authenticate with Twist. Once approved, the token is stored in your OS credential manager:
34
+
35
+ - macOS: Keychain
36
+ - Windows: Credential Manager
37
+ - Linux: Secret Service/libsecret
38
+
39
+ If secure storage is unavailable, the CLI warns and falls back to `~/.config/twist-cli/config.json`. Existing plaintext tokens are migrated automatically the next time the CLI reads them successfully from the config file. Non-secret settings such as the current workspace remain in the config file.
40
+
41
+ ### Alternative methods
42
+
43
+ **Manual token:**
44
+
45
+ ```bash
46
+ tw auth token "your-token"
47
+ ```
48
+
49
+ **Environment variable:**
50
+
51
+ ```bash
52
+ export TWIST_API_TOKEN="your-token"
53
+ ```
54
+
55
+ `TWIST_API_TOKEN` always takes priority over the stored token.
56
+
57
+ ### Auth commands
58
+
59
+ ```bash
60
+ tw auth status # check if authenticated
61
+ tw auth logout # remove saved token
62
+ ```
63
+
35
64
  ## Usage
36
65
 
37
66
  ```bash
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA8HnC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAc1D"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAwInC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAc1D"}
@@ -3,7 +3,6 @@ import chalk from 'chalk';
3
3
  import open from 'open';
4
4
  import { getSessionUser } from '../lib/api.js';
5
5
  import { clearApiToken, saveApiToken } from '../lib/auth.js';
6
- import { getConfigPath } from '../lib/config.js';
7
6
  import { buildAuthorizationUrl, exchangeCodeForToken, registerDynamicClient } from '../lib/oauth.js';
8
7
  import { startCallbackServer } from '../lib/oauth-server.js';
9
8
  import { generateCodeChallenge, generateCodeVerifier, generateState } from '../lib/pkce.js';
@@ -38,10 +37,9 @@ async function loginWithOAuth() {
38
37
  cleanup = result.cleanup;
39
38
  console.log(chalk.dim('Exchanging authorization code for token...'));
40
39
  const accessToken = await exchangeCodeForToken(result.code, codeVerifier, client);
41
- // Save token using existing logic
42
- await saveApiToken(accessToken);
40
+ const saveResult = await saveApiToken(accessToken);
43
41
  console.log(chalk.green('✓'), 'OAuth authentication successful!');
44
- console.log(chalk.dim(`Token saved to ${getConfigPath()}`));
42
+ logTokenStorageResult(saveResult, 'Token stored securely in the system credential manager');
45
43
  }
46
44
  finally {
47
45
  // Always cleanup the server
@@ -85,10 +83,9 @@ async function loginWithToken(token) {
85
83
  return;
86
84
  }
87
85
  }
88
- // Save token to config
89
- await saveApiToken(token.trim());
86
+ const saveResult = await saveApiToken(token.trim());
90
87
  console.log(chalk.green('✓'), 'API token saved successfully!');
91
- console.log(chalk.dim(`Token saved to ${getConfigPath()}`));
88
+ logTokenStorageResult(saveResult, 'Token stored securely in the system credential manager');
92
89
  }
93
90
  async function showStatus() {
94
91
  try {
@@ -104,9 +101,19 @@ async function showStatus() {
104
101
  }
105
102
  }
106
103
  async function logout() {
107
- await clearApiToken();
104
+ const clearResult = await clearApiToken();
108
105
  console.log(chalk.green('✓'), 'Logged out');
109
- console.log(chalk.dim(`Token removed from ${getConfigPath()}`));
106
+ logTokenStorageResult(clearResult, 'Stored token removed from the system credential manager');
107
+ }
108
+ function logTokenStorageResult(result, secureStoreMessage) {
109
+ if (result.storage === 'secure-store') {
110
+ console.log(chalk.dim(secureStoreMessage));
111
+ if (result.warning) {
112
+ console.error(chalk.yellow('Warning:'), result.warning);
113
+ }
114
+ return;
115
+ }
116
+ console.error(chalk.yellow('Warning:'), result.warning);
110
117
  }
111
118
  export function registerAuthCommand(program) {
112
119
  const auth = program.command('auth').description('Manage authentication');
@@ -114,7 +121,7 @@ export function registerAuthCommand(program) {
114
121
  .description('Authenticate using OAuth (opens browser)')
115
122
  .action(loginWithOAuth);
116
123
  auth.command('token [token]')
117
- .description('Save API token to config file (manual method)')
124
+ .description('Save API token for CLI authentication')
118
125
  .action(loginWithToken);
119
126
  auth.command('status').description('Show current authentication status').action(showStatus);
120
127
  auth.command('logout').description('Remove saved authentication token').action(logout);
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,10 @@
1
+ export declare const TOKEN_ENV_VAR = "TWIST_API_TOKEN";
2
+ export type TokenStorageLocation = 'secure-store' | 'config-file';
3
+ export interface TokenStorageResult {
4
+ storage: TokenStorageLocation;
5
+ warning?: string;
6
+ }
1
7
  export declare function getApiToken(): Promise<string>;
2
- export declare function saveApiToken(token: string): Promise<void>;
3
- export declare function clearApiToken(): Promise<void>;
8
+ export declare function saveApiToken(token: string): Promise<TokenStorageResult>;
9
+ export declare function clearApiToken(): Promise<TokenStorageResult>;
4
10
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAcnD;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ/D;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBnD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,aAAa,oBAAoB,CAAA;AAC9C,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,aAAa,CAAA;AAEjE,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,oBAAoB,CAAA;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAmEnD;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA4B7E;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAmBjE"}
package/dist/lib/auth.js CHANGED
@@ -1,40 +1,155 @@
1
1
  import { unlink } from 'node:fs/promises';
2
- import { getConfig, getConfigPath, setConfig, updateConfig } from './config.js';
2
+ import { getConfig, getConfigPath, setConfig } from './config.js';
3
+ import { createSecureStore, SECURE_STORE_DESCRIPTION, SecureStoreUnavailableError, } from './secure-store.js';
4
+ export const TOKEN_ENV_VAR = 'TWIST_API_TOKEN';
3
5
  export async function getApiToken() {
4
- const envToken = process.env.TWIST_API_TOKEN;
6
+ // Priority 1: Environment variable
7
+ const envToken = process.env[TOKEN_ENV_VAR];
5
8
  if (envToken) {
6
9
  return envToken;
7
10
  }
8
11
  const config = await getConfig();
9
- if (config.token) {
10
- return config.token;
12
+ const configToken = getConfigToken(config);
13
+ const secureStore = createSecureStore();
14
+ if (configToken) {
15
+ try {
16
+ await secureStore.setSecret(configToken);
17
+ const cleanupWarning = await cleanupAuthFallbackState(config, 'Token was migrated to secure storage,');
18
+ if (cleanupWarning) {
19
+ warn(cleanupWarning);
20
+ }
21
+ }
22
+ catch (error) {
23
+ if (error instanceof SecureStoreUnavailableError) {
24
+ warnSecureStoreFallback('using plaintext token from');
25
+ }
26
+ else {
27
+ throw error;
28
+ }
29
+ }
30
+ return configToken;
31
+ }
32
+ if (config.pendingSecureStoreClear) {
33
+ try {
34
+ await secureStore.deleteSecret();
35
+ const cleanupWarning = await cleanupAuthFallbackState(config, 'Secure-store token was removed,');
36
+ if (cleanupWarning) {
37
+ warn(cleanupWarning);
38
+ }
39
+ }
40
+ catch (error) {
41
+ if (!(error instanceof SecureStoreUnavailableError)) {
42
+ throw error;
43
+ }
44
+ }
45
+ throw new Error(`No API token found. Set ${TOKEN_ENV_VAR} or run \`tw auth login\` or \`tw auth token <token>\`.`);
11
46
  }
12
- throw new Error(`No API token found. Set TWIST_API_TOKEN environment variable or add "token" to ${getConfigPath()}`);
47
+ try {
48
+ const storedToken = await secureStore.getSecret();
49
+ if (storedToken?.trim()) {
50
+ return storedToken;
51
+ }
52
+ }
53
+ catch (error) {
54
+ if (!(error instanceof SecureStoreUnavailableError)) {
55
+ throw error;
56
+ }
57
+ }
58
+ throw new Error(`No API token found. Set ${TOKEN_ENV_VAR} or run \`tw auth login\` or \`tw auth token <token>\`.`);
13
59
  }
14
60
  export async function saveApiToken(token) {
15
61
  // Validate token (non-empty, reasonable length)
16
62
  if (!token || token.trim().length < 10) {
17
63
  throw new Error('Invalid token: Token must be at least 10 characters');
18
64
  }
19
- // Update config with new token using the existing config system
20
- await updateConfig({ token: token.trim() });
65
+ const trimmedToken = token.trim();
66
+ const secureStore = createSecureStore();
67
+ try {
68
+ await secureStore.setSecret(trimmedToken);
69
+ const existingConfig = await getConfig();
70
+ const warning = await cleanupAuthFallbackState(existingConfig, 'Token was stored securely,');
71
+ return warning ? { storage: 'secure-store', warning } : { storage: 'secure-store' };
72
+ }
73
+ catch (error) {
74
+ if (!(error instanceof SecureStoreUnavailableError)) {
75
+ throw error;
76
+ }
77
+ }
78
+ const config = await getConfig();
79
+ config.token = trimmedToken;
80
+ delete config.pendingSecureStoreClear;
81
+ await writeConfig(config);
82
+ return {
83
+ storage: 'config-file',
84
+ warning: buildFallbackWarning('token saved as plaintext in'),
85
+ };
21
86
  }
22
87
  export async function clearApiToken() {
23
88
  const config = await getConfig();
24
- // Remove the token from config
25
- delete config.token;
26
- // If config is empty after removing the token, delete the config file
89
+ const secureStore = createSecureStore();
90
+ try {
91
+ await secureStore.deleteSecret();
92
+ const warning = await cleanupAuthFallbackState(config, 'Secure-store token was removed,');
93
+ return warning ? { storage: 'secure-store', warning } : { storage: 'secure-store' };
94
+ }
95
+ catch (error) {
96
+ if (!(error instanceof SecureStoreUnavailableError)) {
97
+ throw error;
98
+ }
99
+ }
100
+ await writeConfig(withPendingSecureStoreClear(config));
101
+ return {
102
+ storage: 'config-file',
103
+ warning: buildFallbackWarning('local auth state cleared in'),
104
+ };
105
+ }
106
+ async function writeConfig(config) {
27
107
  if (Object.keys(config).length === 0) {
28
108
  try {
29
109
  await unlink(getConfigPath());
30
110
  }
31
- catch {
32
- // Config file doesn't exist, nothing to delete
111
+ catch (error) {
112
+ if (!isMissingFileError(error)) {
113
+ throw error;
114
+ }
33
115
  }
116
+ return;
34
117
  }
35
- else {
36
- // Otherwise, save the config without the token
37
- await setConfig(config);
118
+ await setConfig(config);
119
+ }
120
+ async function cleanupAuthFallbackState(config, warningPrefix) {
121
+ try {
122
+ await writeConfig(withoutAuthFallbackState(config));
123
+ return undefined;
124
+ }
125
+ catch (error) {
126
+ return buildConfigCleanupWarning(warningPrefix, error);
38
127
  }
39
128
  }
129
+ function getConfigToken(config) {
130
+ return typeof config.token === 'string' && config.token.trim() ? config.token.trim() : null;
131
+ }
132
+ function withoutAuthFallbackState(config) {
133
+ const { token: _token, pendingSecureStoreClear: _pending, ...rest } = config;
134
+ return rest;
135
+ }
136
+ function withPendingSecureStoreClear(config) {
137
+ return { ...withoutAuthFallbackState(config), pendingSecureStoreClear: true };
138
+ }
139
+ function buildFallbackWarning(action) {
140
+ return `${SECURE_STORE_DESCRIPTION} unavailable; ${action} ${getConfigPath()}`;
141
+ }
142
+ function buildConfigCleanupWarning(prefix, error) {
143
+ const detail = error instanceof Error && error.message ? ` (${error.message})` : '';
144
+ return `${prefix} but could not remove legacy plaintext token from ${getConfigPath()}${detail}`;
145
+ }
146
+ function isMissingFileError(error) {
147
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
148
+ }
149
+ function warn(message) {
150
+ console.error(`Warning: ${message}`);
151
+ }
152
+ function warnSecureStoreFallback(action) {
153
+ warn(buildFallbackWarning(action));
154
+ }
40
155
  //# sourceMappingURL=auth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/E,MAAM,CAAC,KAAK,UAAU,WAAW;IAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAA;IAC5C,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,QAAQ,CAAA;IACnB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAA;IAChC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,MAAM,CAAC,KAAK,CAAA;IACvB,CAAC;IAED,MAAM,IAAI,KAAK,CACX,kFAAkF,aAAa,EAAE,EAAE,CACtG,CAAA;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa;IAC5C,gDAAgD;IAChD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAA;IAC1E,CAAC;IAED,gEAAgE;IAChE,MAAM,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;AAC/C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IAC/B,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAA;IAEhC,+BAA+B;IAC/B,OAAO,MAAM,CAAC,KAAK,CAAA;IAEnB,sEAAsE;IACtE,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,IAAI,CAAC;YACD,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC,CAAA;QACjC,CAAC;QAAC,MAAM,CAAC;YACL,+CAA+C;QACnD,CAAC;IACL,CAAC;SAAM,CAAC;QACJ,+CAA+C;QAC/C,MAAM,SAAS,CAAC,MAAM,CAAC,CAAA;IAC3B,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  export interface Config {
2
2
  token?: string;
3
+ pendingSecureStoreClear?: boolean;
3
4
  currentWorkspace?: number;
4
5
  }
5
6
  export declare function getConfig(): Promise<Config>;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,MAAM;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAOjD;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,MAAM;IAEnB,KAAK,CAAC,EAAE,MAAM,CAAA;IAEd,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAOjD;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CAAA;AAO1E,MAAM,CAAC,KAAK,UAAU,SAAS;IAC3B,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAW,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAA;IACb,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAChC,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAwB;IACvD,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAA;IAChC,MAAM,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,aAAa;IACzB,OAAO,WAAW,CAAA;AACtB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,CAAC,CAAA;AAU1E,MAAM,CAAC,KAAK,UAAU,SAAS;IAC3B,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;QACpD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAW,CAAA;IACxC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAA;IACb,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAc;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAChC,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACrC,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAwB;IACvD,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAA;IAChC,MAAM,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,aAAa;IACzB,OAAO,WAAW,CAAA;AACtB,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare const SECURE_STORE_DESCRIPTION = "system credential manager";
2
+ export declare class SecureStoreUnavailableError extends Error {
3
+ constructor(message?: string);
4
+ }
5
+ export interface SecureStore {
6
+ getSecret(): Promise<string | null>;
7
+ setSecret(secret: string): Promise<void>;
8
+ deleteSecret(): Promise<boolean>;
9
+ }
10
+ export declare function createSecureStore(): SecureStore;
11
+ //# sourceMappingURL=secure-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-store.d.ts","sourceRoot":"","sources":["../../src/lib/secure-store.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,wBAAwB,8BAA8B,CAAA;AAEnE,qBAAa,2BAA4B,SAAQ,KAAK;gBACtC,OAAO,SAA6C;CAInE;AAED,MAAM,WAAW,WAAW;IACxB,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACnC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CACnC;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CA6B/C"}
@@ -0,0 +1,57 @@
1
+ const SERVICE_NAME = 'twist-cli';
2
+ const ACCOUNT_NAME = 'api-token';
3
+ export const SECURE_STORE_DESCRIPTION = 'system credential manager';
4
+ export class SecureStoreUnavailableError extends Error {
5
+ constructor(message = 'System credential storage is unavailable') {
6
+ super(message);
7
+ this.name = 'SecureStoreUnavailableError';
8
+ }
9
+ }
10
+ export function createSecureStore() {
11
+ return {
12
+ async getSecret() {
13
+ const entry = await getEntry();
14
+ try {
15
+ return (await entry.getPassword()) ?? null;
16
+ }
17
+ catch (error) {
18
+ throw toUnavailableError(error);
19
+ }
20
+ },
21
+ async setSecret(secret) {
22
+ const entry = await getEntry();
23
+ try {
24
+ await entry.setPassword(secret);
25
+ }
26
+ catch (error) {
27
+ throw toUnavailableError(error);
28
+ }
29
+ },
30
+ async deleteSecret() {
31
+ const entry = await getEntry();
32
+ try {
33
+ return await entry.deleteCredential();
34
+ }
35
+ catch (error) {
36
+ throw toUnavailableError(error);
37
+ }
38
+ },
39
+ };
40
+ }
41
+ async function getEntry() {
42
+ try {
43
+ const { AsyncEntry } = await import('@napi-rs/keyring');
44
+ return new AsyncEntry(SERVICE_NAME, ACCOUNT_NAME);
45
+ }
46
+ catch (error) {
47
+ throw toUnavailableError(error);
48
+ }
49
+ }
50
+ function toUnavailableError(error) {
51
+ if (error instanceof SecureStoreUnavailableError) {
52
+ return error;
53
+ }
54
+ const message = error instanceof Error ? error.message : 'System credential storage is unavailable';
55
+ return new SecureStoreUnavailableError(message);
56
+ }
57
+ //# sourceMappingURL=secure-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secure-store.js","sourceRoot":"","sources":["../../src/lib/secure-store.ts"],"names":[],"mappings":"AAAA,MAAM,YAAY,GAAG,WAAW,CAAA;AAChC,MAAM,YAAY,GAAG,WAAW,CAAA;AAEhC,MAAM,CAAC,MAAM,wBAAwB,GAAG,2BAA2B,CAAA;AAEnE,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAClD,YAAY,OAAO,GAAG,0CAA0C;QAC5D,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAA;IAC7C,CAAC;CACJ;AAQD,MAAM,UAAU,iBAAiB;IAC7B,OAAO;QACH,KAAK,CAAC,SAAS;YACX,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAA;YAC9B,IAAI,CAAC;gBACD,OAAO,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAA;YAC9C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAA;YACnC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,SAAS,CAAC,MAAc;YAC1B,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAA;YAC9B,IAAI,CAAC;gBACD,MAAM,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YACnC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAA;YACnC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,YAAY;YACd,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAA;YAC9B,IAAI,CAAC;gBACD,OAAO,MAAM,KAAK,CAAC,gBAAgB,EAAE,CAAA;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAA;YACnC,CAAC;QACL,CAAC;KACJ,CAAA;AACL,CAAC;AAED,KAAK,UAAU,QAAQ;IACnB,IAAI,CAAC;QACD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QACvD,OAAO,IAAI,UAAU,CAAC,YAAY,EAAE,YAAY,CAAC,CAAA;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,MAAM,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACnC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACtC,IAAI,KAAK,YAAY,2BAA2B,EAAE,CAAC;QAC/C,OAAO,KAAK,CAAA;IAChB,CAAC;IAED,MAAM,OAAO,GACT,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,0CAA0C,CAAA;IACvF,OAAO,IAAI,2BAA2B,CAAC,OAAO,CAAC,CAAA;AACnD,CAAC"}
@@ -1,5 +1,5 @@
1
1
  export declare const SKILL_NAME = "twist-cli";
2
2
  export declare const SKILL_DESCRIPTION = "Twist messaging CLI for team communication";
3
- export declare const SKILL_CONTENT = "# Twist CLI Skill\n\nAccess Twist messaging via the `tw` CLI. Use when the user asks about their Twist workspaces, threads, messages, or wants to interact with Twist in any way.\n\n## Setup\n\n```bash\ntw auth login # OAuth login (opens browser)\ntw auth token <your-api-token> # Save API token manually\ntw auth status # Verify authentication\ntw auth logout # Remove saved token\ntw workspaces # List available workspaces\ntw workspace use <ref> # Set current workspace\ntw completion install # Install shell completions\ntw update # Update CLI to latest version\n```\n\n## View by URL\n\n```bash\ntw view <url> # View any Twist entity by URL\n```\n\nRoutes automatically based on URL structure:\n- Message URL \u2192 `tw msg view`\n- Conversation URL \u2192 `tw conversation view`\n- Thread+comment URL \u2192 `tw thread view` (comment ID extracted from URL)\n- Thread URL \u2192 `tw thread view`\n\nAll target command flags pass through (e.g. `--json`, `--raw`, `--full`).\n\n## Inbox\n\n```bash\ntw inbox # Show inbox threads\ntw inbox --unread # Only unread threads\ntw inbox --channel <filter> # Filter by channel name (fuzzy)\ntw inbox --since <date> # Filter by date (ISO format)\ntw inbox --limit <n> # Max items (default: 50)\n```\n\n## Threads\n\n```bash\ntw thread <thread-ref> # View thread (shorthand for view)\ntw thread view <thread-ref> # View thread with comments\ntw thread view <ref> --comment <id> # View a specific comment\ntw thread view <url-with-/c/id> # Comment ID extracted from URL\ntw thread view <ref> --unread # Show only unread comments\ntw thread view <ref> --context 3 # Include 3 read comments before unread\ntw thread view <ref> --limit 20 # Limit number of comments\ntw thread view <ref> --since <date> # Comments newer than date\ntw thread view <ref> --raw # Show raw markdown\ntw thread reply <ref> \"content\" # Post a comment\ntw thread reply <ref> \"content\" --notify EVERYONE # Notify all workspace members\ntw thread reply <ref> \"content\" --notify 123,id:456 # Notify specific user IDs\ntw thread done <ref> # Archive thread (mark done)\n```\n\nDefault `--notify` is EVERYONE_IN_THREAD. Options: EVERYONE, EVERYONE_IN_THREAD, or comma-separated user ID refs.\n\n## Conversations (DMs/Groups)\n\n```bash\ntw conversation unread # List unread conversations\ntw conversation <conversation-ref> # View conversation (shorthand for view)\ntw conversation view <conversation-ref> # View conversation messages\ntw conversation with <user-ref> # Find your 1:1 DM with a user\ntw conversation with <user-ref> --snippet # Include the latest message preview\ntw conversation with <user-ref> --include-groups # List any conversations with that user\ntw conversation reply <ref> \"content\" # Send a message\ntw conversation done <ref> # Archive conversation\n```\n\nAlias: `tw convo` works the same as `tw conversation`.\n\n## Conversation Messages\n\n```bash\ntw msg <message-ref> # View a message (shorthand for view)\ntw msg view <message-ref> # View a single conversation message\ntw msg update <ref> \"content\" # Edit a conversation message\ntw msg delete <ref> # Delete a conversation message\n```\n\nAlias: `tw message` works the same as `tw msg`.\n\n## Search\n\n```bash\ntw search \"query\" # Search content\ntw search \"query\" --type threads # Filter: threads, messages, or all\ntw search \"query\" --author <ref> # Filter by author\ntw search \"query\" --to <ref> # Messages sent to user\ntw search \"query\" --title-only # Search thread titles only\ntw search \"query\" --mention-me # Results mentioning current user\ntw search \"query\" --conversation <refs> # Limit to conversations (comma-separated refs)\ntw search \"query\" --since <date> # Content from date\ntw search \"query\" --until <date> # Content until date\ntw search \"query\" --channel <refs> # Filter by channel refs (comma-separated)\ntw search \"query\" --limit <n> # Max results (default: 50)\ntw search \"query\" --cursor <cur> # Pagination cursor\n```\n\n## Users & Channels\n\n```bash\ntw user # Show current user info\ntw users # List workspace users\ntw users --search <text> # Filter by name/email\ntw channels # List workspace channels\n```\n\n## Reactions\n\n```bash\ntw react thread <ref> \uD83D\uDC4D # Add reaction to thread\ntw react comment <ref> +1 # Add reaction (shortcode)\ntw react message <ref> heart # Add reaction to DM message\ntw unreact thread <ref> \uD83D\uDC4D # Remove reaction\n```\n\nSupported shortcodes: +1, -1, heart, tada, smile, laughing, thinking, fire, check, x, eyes, pray, clap, rocket, wave\n\n## Shell Completions\n\n```bash\ntw completion install # Install tab completions (prompts for shell)\ntw completion install bash # Install for specific shell\ntw completion install zsh\ntw completion install fish\ntw completion uninstall # Remove completions\n```\n\n### Update\n\n```bash\ntw update # Update CLI to latest version\ntw update --check # Check for updates without installing\n```\n\n## Global Options\n\n```bash\n--no-spinner # Disable loading animations\n--progress-jsonl # Machine-readable progress events (JSONL to stderr)\n--accessible # Add text labels to color-coded output (also: TW_ACCESSIBLE=1)\n```\n\n## Output Formats\n\nAll list/view commands support:\n\n```bash\n--json # Output as JSON\n--ndjson # Output as newline-delimited JSON (for streaming)\n--full # Include all fields (default shows essential fields only)\n```\n\n## Reference System\n\nCommands accept flexible references:\n- **Numeric IDs**: `123` or `id:123`\n- **Twist URLs**: Full `https://twist.com/...` URLs (parsed automatically)\n- **Fuzzy names**: For workspaces/users - `\"My Workspace\"` or partial matches\n\n## Common Workflows\n\n**View by URL (auto-routes to the right command):**\n```bash\ntw view https://twist.com/a/1585/ch/100/t/200 # View thread\ntw view https://twist.com/a/1585/ch/100/t/200/c/300 # View comment\ntw view https://twist.com/a/1585/msg/400 # View conversation\ntw view https://twist.com/a/1585/msg/400/m/500 --json # View message as JSON\n```\n\n**Check inbox and respond:**\n```bash\ntw inbox --unread --json\ntw thread view <id> --unread\ntw thread reply <id> \"Thanks, I'll look into this.\"\ntw thread done <id>\n```\n\n**Search and review:**\n```bash\ntw search \"deployment\" --type threads --json\ntw thread view <thread-id>\n```\n\n**Check DMs:**\n```bash\ntw conversation unread --json\ntw conversation view <conversation-id>\ntw conversation with \"Alice Example\"\ntw conversation reply <id> \"Got it, thanks!\"\n```\n";
4
- export declare const SKILL_FILE_CONTENT = "---\nname: twist-cli\ndescription: Twist messaging CLI for team communication\n---\n\n# Twist CLI Skill\n\nAccess Twist messaging via the `tw` CLI. Use when the user asks about their Twist workspaces, threads, messages, or wants to interact with Twist in any way.\n\n## Setup\n\n```bash\ntw auth login # OAuth login (opens browser)\ntw auth token <your-api-token> # Save API token manually\ntw auth status # Verify authentication\ntw auth logout # Remove saved token\ntw workspaces # List available workspaces\ntw workspace use <ref> # Set current workspace\ntw completion install # Install shell completions\ntw update # Update CLI to latest version\n```\n\n## View by URL\n\n```bash\ntw view <url> # View any Twist entity by URL\n```\n\nRoutes automatically based on URL structure:\n- Message URL \u2192 `tw msg view`\n- Conversation URL \u2192 `tw conversation view`\n- Thread+comment URL \u2192 `tw thread view` (comment ID extracted from URL)\n- Thread URL \u2192 `tw thread view`\n\nAll target command flags pass through (e.g. `--json`, `--raw`, `--full`).\n\n## Inbox\n\n```bash\ntw inbox # Show inbox threads\ntw inbox --unread # Only unread threads\ntw inbox --channel <filter> # Filter by channel name (fuzzy)\ntw inbox --since <date> # Filter by date (ISO format)\ntw inbox --limit <n> # Max items (default: 50)\n```\n\n## Threads\n\n```bash\ntw thread <thread-ref> # View thread (shorthand for view)\ntw thread view <thread-ref> # View thread with comments\ntw thread view <ref> --comment <id> # View a specific comment\ntw thread view <url-with-/c/id> # Comment ID extracted from URL\ntw thread view <ref> --unread # Show only unread comments\ntw thread view <ref> --context 3 # Include 3 read comments before unread\ntw thread view <ref> --limit 20 # Limit number of comments\ntw thread view <ref> --since <date> # Comments newer than date\ntw thread view <ref> --raw # Show raw markdown\ntw thread reply <ref> \"content\" # Post a comment\ntw thread reply <ref> \"content\" --notify EVERYONE # Notify all workspace members\ntw thread reply <ref> \"content\" --notify 123,id:456 # Notify specific user IDs\ntw thread done <ref> # Archive thread (mark done)\n```\n\nDefault `--notify` is EVERYONE_IN_THREAD. Options: EVERYONE, EVERYONE_IN_THREAD, or comma-separated user ID refs.\n\n## Conversations (DMs/Groups)\n\n```bash\ntw conversation unread # List unread conversations\ntw conversation <conversation-ref> # View conversation (shorthand for view)\ntw conversation view <conversation-ref> # View conversation messages\ntw conversation with <user-ref> # Find your 1:1 DM with a user\ntw conversation with <user-ref> --snippet # Include the latest message preview\ntw conversation with <user-ref> --include-groups # List any conversations with that user\ntw conversation reply <ref> \"content\" # Send a message\ntw conversation done <ref> # Archive conversation\n```\n\nAlias: `tw convo` works the same as `tw conversation`.\n\n## Conversation Messages\n\n```bash\ntw msg <message-ref> # View a message (shorthand for view)\ntw msg view <message-ref> # View a single conversation message\ntw msg update <ref> \"content\" # Edit a conversation message\ntw msg delete <ref> # Delete a conversation message\n```\n\nAlias: `tw message` works the same as `tw msg`.\n\n## Search\n\n```bash\ntw search \"query\" # Search content\ntw search \"query\" --type threads # Filter: threads, messages, or all\ntw search \"query\" --author <ref> # Filter by author\ntw search \"query\" --to <ref> # Messages sent to user\ntw search \"query\" --title-only # Search thread titles only\ntw search \"query\" --mention-me # Results mentioning current user\ntw search \"query\" --conversation <refs> # Limit to conversations (comma-separated refs)\ntw search \"query\" --since <date> # Content from date\ntw search \"query\" --until <date> # Content until date\ntw search \"query\" --channel <refs> # Filter by channel refs (comma-separated)\ntw search \"query\" --limit <n> # Max results (default: 50)\ntw search \"query\" --cursor <cur> # Pagination cursor\n```\n\n## Users & Channels\n\n```bash\ntw user # Show current user info\ntw users # List workspace users\ntw users --search <text> # Filter by name/email\ntw channels # List workspace channels\n```\n\n## Reactions\n\n```bash\ntw react thread <ref> \uD83D\uDC4D # Add reaction to thread\ntw react comment <ref> +1 # Add reaction (shortcode)\ntw react message <ref> heart # Add reaction to DM message\ntw unreact thread <ref> \uD83D\uDC4D # Remove reaction\n```\n\nSupported shortcodes: +1, -1, heart, tada, smile, laughing, thinking, fire, check, x, eyes, pray, clap, rocket, wave\n\n## Shell Completions\n\n```bash\ntw completion install # Install tab completions (prompts for shell)\ntw completion install bash # Install for specific shell\ntw completion install zsh\ntw completion install fish\ntw completion uninstall # Remove completions\n```\n\n### Update\n\n```bash\ntw update # Update CLI to latest version\ntw update --check # Check for updates without installing\n```\n\n## Global Options\n\n```bash\n--no-spinner # Disable loading animations\n--progress-jsonl # Machine-readable progress events (JSONL to stderr)\n--accessible # Add text labels to color-coded output (also: TW_ACCESSIBLE=1)\n```\n\n## Output Formats\n\nAll list/view commands support:\n\n```bash\n--json # Output as JSON\n--ndjson # Output as newline-delimited JSON (for streaming)\n--full # Include all fields (default shows essential fields only)\n```\n\n## Reference System\n\nCommands accept flexible references:\n- **Numeric IDs**: `123` or `id:123`\n- **Twist URLs**: Full `https://twist.com/...` URLs (parsed automatically)\n- **Fuzzy names**: For workspaces/users - `\"My Workspace\"` or partial matches\n\n## Common Workflows\n\n**View by URL (auto-routes to the right command):**\n```bash\ntw view https://twist.com/a/1585/ch/100/t/200 # View thread\ntw view https://twist.com/a/1585/ch/100/t/200/c/300 # View comment\ntw view https://twist.com/a/1585/msg/400 # View conversation\ntw view https://twist.com/a/1585/msg/400/m/500 --json # View message as JSON\n```\n\n**Check inbox and respond:**\n```bash\ntw inbox --unread --json\ntw thread view <id> --unread\ntw thread reply <id> \"Thanks, I'll look into this.\"\ntw thread done <id>\n```\n\n**Search and review:**\n```bash\ntw search \"deployment\" --type threads --json\ntw thread view <thread-id>\n```\n\n**Check DMs:**\n```bash\ntw conversation unread --json\ntw conversation view <conversation-id>\ntw conversation with \"Alice Example\"\ntw conversation reply <id> \"Got it, thanks!\"\n```\n";
3
+ export declare const SKILL_CONTENT = "# Twist CLI Skill\n\nAccess Twist messaging via the `tw` CLI. Use when the user asks about their Twist workspaces, threads, messages, or wants to interact with Twist in any way.\n\n## Setup\n\n```bash\ntw auth login # OAuth login (opens browser)\ntw auth token <your-api-token> # Save API token manually\ntw auth status # Verify authentication\ntw auth logout # Remove saved token\ntw workspaces # List available workspaces\ntw workspace use <ref> # Set current workspace\ntw completion install # Install shell completions\ntw update # Update CLI to latest version\n```\n\nStored auth uses the system credential manager when available. If secure storage is unavailable, `tw` warns and falls back to `~/.config/twist-cli/config.json`. `TWIST_API_TOKEN` always takes priority over the stored token, and legacy plaintext config tokens are migrated automatically when secure storage is available.\n\n## View by URL\n\n```bash\ntw view <url> # View any Twist entity by URL\n```\n\nRoutes automatically based on URL structure:\n- Message URL \u2192 `tw msg view`\n- Conversation URL \u2192 `tw conversation view`\n- Thread+comment URL \u2192 `tw thread view` (comment ID extracted from URL)\n- Thread URL \u2192 `tw thread view`\n\nAll target command flags pass through (e.g. `--json`, `--raw`, `--full`).\n\n## Inbox\n\n```bash\ntw inbox # Show inbox threads\ntw inbox --unread # Only unread threads\ntw inbox --channel <filter> # Filter by channel name (fuzzy)\ntw inbox --since <date> # Filter by date (ISO format)\ntw inbox --limit <n> # Max items (default: 50)\n```\n\n## Threads\n\n```bash\ntw thread <thread-ref> # View thread (shorthand for view)\ntw thread view <thread-ref> # View thread with comments\ntw thread view <ref> --comment <id> # View a specific comment\ntw thread view <url-with-/c/id> # Comment ID extracted from URL\ntw thread view <ref> --unread # Show only unread comments\ntw thread view <ref> --context 3 # Include 3 read comments before unread\ntw thread view <ref> --limit 20 # Limit number of comments\ntw thread view <ref> --since <date> # Comments newer than date\ntw thread view <ref> --raw # Show raw markdown\ntw thread reply <ref> \"content\" # Post a comment\ntw thread reply <ref> \"content\" --notify EVERYONE # Notify all workspace members\ntw thread reply <ref> \"content\" --notify 123,id:456 # Notify specific user IDs\ntw thread done <ref> # Archive thread (mark done)\n```\n\nDefault `--notify` is EVERYONE_IN_THREAD. Options: EVERYONE, EVERYONE_IN_THREAD, or comma-separated user ID refs.\n\n## Conversations (DMs/Groups)\n\n```bash\ntw conversation unread # List unread conversations\ntw conversation <conversation-ref> # View conversation (shorthand for view)\ntw conversation view <conversation-ref> # View conversation messages\ntw conversation with <user-ref> # Find your 1:1 DM with a user\ntw conversation with <user-ref> --snippet # Include the latest message preview\ntw conversation with <user-ref> --include-groups # List any conversations with that user\ntw conversation reply <ref> \"content\" # Send a message\ntw conversation done <ref> # Archive conversation\n```\n\nAlias: `tw convo` works the same as `tw conversation`.\n\n## Conversation Messages\n\n```bash\ntw msg <message-ref> # View a message (shorthand for view)\ntw msg view <message-ref> # View a single conversation message\ntw msg update <ref> \"content\" # Edit a conversation message\ntw msg delete <ref> # Delete a conversation message\n```\n\nAlias: `tw message` works the same as `tw msg`.\n\n## Search\n\n```bash\ntw search \"query\" # Search content\ntw search \"query\" --type threads # Filter: threads, messages, or all\ntw search \"query\" --author <ref> # Filter by author\ntw search \"query\" --to <ref> # Messages sent to user\ntw search \"query\" --title-only # Search thread titles only\ntw search \"query\" --mention-me # Results mentioning current user\ntw search \"query\" --conversation <refs> # Limit to conversations (comma-separated refs)\ntw search \"query\" --since <date> # Content from date\ntw search \"query\" --until <date> # Content until date\ntw search \"query\" --channel <refs> # Filter by channel refs (comma-separated)\ntw search \"query\" --limit <n> # Max results (default: 50)\ntw search \"query\" --cursor <cur> # Pagination cursor\n```\n\n## Users & Channels\n\n```bash\ntw user # Show current user info\ntw users # List workspace users\ntw users --search <text> # Filter by name/email\ntw channels # List workspace channels\n```\n\n## Reactions\n\n```bash\ntw react thread <ref> \uD83D\uDC4D # Add reaction to thread\ntw react comment <ref> +1 # Add reaction (shortcode)\ntw react message <ref> heart # Add reaction to DM message\ntw unreact thread <ref> \uD83D\uDC4D # Remove reaction\n```\n\nSupported shortcodes: +1, -1, heart, tada, smile, laughing, thinking, fire, check, x, eyes, pray, clap, rocket, wave\n\n## Shell Completions\n\n```bash\ntw completion install # Install tab completions (prompts for shell)\ntw completion install bash # Install for specific shell\ntw completion install zsh\ntw completion install fish\ntw completion uninstall # Remove completions\n```\n\n### Update\n\n```bash\ntw update # Update CLI to latest version\ntw update --check # Check for updates without installing\n```\n\n## Global Options\n\n```bash\n--no-spinner # Disable loading animations\n--progress-jsonl # Machine-readable progress events (JSONL to stderr)\n--accessible # Add text labels to color-coded output (also: TW_ACCESSIBLE=1)\n```\n\n## Output Formats\n\nAll list/view commands support:\n\n```bash\n--json # Output as JSON\n--ndjson # Output as newline-delimited JSON (for streaming)\n--full # Include all fields (default shows essential fields only)\n```\n\n## Reference System\n\nCommands accept flexible references:\n- **Numeric IDs**: `123` or `id:123`\n- **Twist URLs**: Full `https://twist.com/...` URLs (parsed automatically)\n- **Fuzzy names**: For workspaces/users - `\"My Workspace\"` or partial matches\n\n## Common Workflows\n\n**View by URL (auto-routes to the right command):**\n```bash\ntw view https://twist.com/a/1585/ch/100/t/200 # View thread\ntw view https://twist.com/a/1585/ch/100/t/200/c/300 # View comment\ntw view https://twist.com/a/1585/msg/400 # View conversation\ntw view https://twist.com/a/1585/msg/400/m/500 --json # View message as JSON\n```\n\n**Check inbox and respond:**\n```bash\ntw inbox --unread --json\ntw thread view <id> --unread\ntw thread reply <id> \"Thanks, I'll look into this.\"\ntw thread done <id>\n```\n\n**Search and review:**\n```bash\ntw search \"deployment\" --type threads --json\ntw thread view <thread-id>\n```\n\n**Check DMs:**\n```bash\ntw conversation unread --json\ntw conversation view <conversation-id>\ntw conversation with \"Alice Example\"\ntw conversation reply <id> \"Got it, thanks!\"\n```\n";
4
+ export declare const SKILL_FILE_CONTENT = "---\nname: twist-cli\ndescription: Twist messaging CLI for team communication\n---\n\n# Twist CLI Skill\n\nAccess Twist messaging via the `tw` CLI. Use when the user asks about their Twist workspaces, threads, messages, or wants to interact with Twist in any way.\n\n## Setup\n\n```bash\ntw auth login # OAuth login (opens browser)\ntw auth token <your-api-token> # Save API token manually\ntw auth status # Verify authentication\ntw auth logout # Remove saved token\ntw workspaces # List available workspaces\ntw workspace use <ref> # Set current workspace\ntw completion install # Install shell completions\ntw update # Update CLI to latest version\n```\n\nStored auth uses the system credential manager when available. If secure storage is unavailable, `tw` warns and falls back to `~/.config/twist-cli/config.json`. `TWIST_API_TOKEN` always takes priority over the stored token, and legacy plaintext config tokens are migrated automatically when secure storage is available.\n\n## View by URL\n\n```bash\ntw view <url> # View any Twist entity by URL\n```\n\nRoutes automatically based on URL structure:\n- Message URL \u2192 `tw msg view`\n- Conversation URL \u2192 `tw conversation view`\n- Thread+comment URL \u2192 `tw thread view` (comment ID extracted from URL)\n- Thread URL \u2192 `tw thread view`\n\nAll target command flags pass through (e.g. `--json`, `--raw`, `--full`).\n\n## Inbox\n\n```bash\ntw inbox # Show inbox threads\ntw inbox --unread # Only unread threads\ntw inbox --channel <filter> # Filter by channel name (fuzzy)\ntw inbox --since <date> # Filter by date (ISO format)\ntw inbox --limit <n> # Max items (default: 50)\n```\n\n## Threads\n\n```bash\ntw thread <thread-ref> # View thread (shorthand for view)\ntw thread view <thread-ref> # View thread with comments\ntw thread view <ref> --comment <id> # View a specific comment\ntw thread view <url-with-/c/id> # Comment ID extracted from URL\ntw thread view <ref> --unread # Show only unread comments\ntw thread view <ref> --context 3 # Include 3 read comments before unread\ntw thread view <ref> --limit 20 # Limit number of comments\ntw thread view <ref> --since <date> # Comments newer than date\ntw thread view <ref> --raw # Show raw markdown\ntw thread reply <ref> \"content\" # Post a comment\ntw thread reply <ref> \"content\" --notify EVERYONE # Notify all workspace members\ntw thread reply <ref> \"content\" --notify 123,id:456 # Notify specific user IDs\ntw thread done <ref> # Archive thread (mark done)\n```\n\nDefault `--notify` is EVERYONE_IN_THREAD. Options: EVERYONE, EVERYONE_IN_THREAD, or comma-separated user ID refs.\n\n## Conversations (DMs/Groups)\n\n```bash\ntw conversation unread # List unread conversations\ntw conversation <conversation-ref> # View conversation (shorthand for view)\ntw conversation view <conversation-ref> # View conversation messages\ntw conversation with <user-ref> # Find your 1:1 DM with a user\ntw conversation with <user-ref> --snippet # Include the latest message preview\ntw conversation with <user-ref> --include-groups # List any conversations with that user\ntw conversation reply <ref> \"content\" # Send a message\ntw conversation done <ref> # Archive conversation\n```\n\nAlias: `tw convo` works the same as `tw conversation`.\n\n## Conversation Messages\n\n```bash\ntw msg <message-ref> # View a message (shorthand for view)\ntw msg view <message-ref> # View a single conversation message\ntw msg update <ref> \"content\" # Edit a conversation message\ntw msg delete <ref> # Delete a conversation message\n```\n\nAlias: `tw message` works the same as `tw msg`.\n\n## Search\n\n```bash\ntw search \"query\" # Search content\ntw search \"query\" --type threads # Filter: threads, messages, or all\ntw search \"query\" --author <ref> # Filter by author\ntw search \"query\" --to <ref> # Messages sent to user\ntw search \"query\" --title-only # Search thread titles only\ntw search \"query\" --mention-me # Results mentioning current user\ntw search \"query\" --conversation <refs> # Limit to conversations (comma-separated refs)\ntw search \"query\" --since <date> # Content from date\ntw search \"query\" --until <date> # Content until date\ntw search \"query\" --channel <refs> # Filter by channel refs (comma-separated)\ntw search \"query\" --limit <n> # Max results (default: 50)\ntw search \"query\" --cursor <cur> # Pagination cursor\n```\n\n## Users & Channels\n\n```bash\ntw user # Show current user info\ntw users # List workspace users\ntw users --search <text> # Filter by name/email\ntw channels # List workspace channels\n```\n\n## Reactions\n\n```bash\ntw react thread <ref> \uD83D\uDC4D # Add reaction to thread\ntw react comment <ref> +1 # Add reaction (shortcode)\ntw react message <ref> heart # Add reaction to DM message\ntw unreact thread <ref> \uD83D\uDC4D # Remove reaction\n```\n\nSupported shortcodes: +1, -1, heart, tada, smile, laughing, thinking, fire, check, x, eyes, pray, clap, rocket, wave\n\n## Shell Completions\n\n```bash\ntw completion install # Install tab completions (prompts for shell)\ntw completion install bash # Install for specific shell\ntw completion install zsh\ntw completion install fish\ntw completion uninstall # Remove completions\n```\n\n### Update\n\n```bash\ntw update # Update CLI to latest version\ntw update --check # Check for updates without installing\n```\n\n## Global Options\n\n```bash\n--no-spinner # Disable loading animations\n--progress-jsonl # Machine-readable progress events (JSONL to stderr)\n--accessible # Add text labels to color-coded output (also: TW_ACCESSIBLE=1)\n```\n\n## Output Formats\n\nAll list/view commands support:\n\n```bash\n--json # Output as JSON\n--ndjson # Output as newline-delimited JSON (for streaming)\n--full # Include all fields (default shows essential fields only)\n```\n\n## Reference System\n\nCommands accept flexible references:\n- **Numeric IDs**: `123` or `id:123`\n- **Twist URLs**: Full `https://twist.com/...` URLs (parsed automatically)\n- **Fuzzy names**: For workspaces/users - `\"My Workspace\"` or partial matches\n\n## Common Workflows\n\n**View by URL (auto-routes to the right command):**\n```bash\ntw view https://twist.com/a/1585/ch/100/t/200 # View thread\ntw view https://twist.com/a/1585/ch/100/t/200/c/300 # View comment\ntw view https://twist.com/a/1585/msg/400 # View conversation\ntw view https://twist.com/a/1585/msg/400/m/500 --json # View message as JSON\n```\n\n**Check inbox and respond:**\n```bash\ntw inbox --unread --json\ntw thread view <id> --unread\ntw thread reply <id> \"Thanks, I'll look into this.\"\ntw thread done <id>\n```\n\n**Search and review:**\n```bash\ntw search \"deployment\" --type threads --json\ntw thread view <thread-id>\n```\n\n**Check DMs:**\n```bash\ntw conversation unread --json\ntw conversation view <conversation-id>\ntw conversation with \"Alice Example\"\ntw conversation reply <id> \"Got it, thanks!\"\n```\n";
5
5
  //# sourceMappingURL=content.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../../src/lib/skills/content.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,cAAc,CAAA;AAErC,eAAO,MAAM,iBAAiB,+CAA+C,CAAA;AAE7E,eAAO,MAAM,aAAa,s7NAqMzB,CAAA;AAED,eAAO,MAAM,kBAAkB,4gOAKd,CAAA"}
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../../src/lib/skills/content.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,cAAc,CAAA;AAErC,eAAO,MAAM,iBAAiB,+CAA+C,CAAA;AAE7E,eAAO,MAAM,aAAa,yvOAuMzB,CAAA;AAED,eAAO,MAAM,kBAAkB,+0OAKd,CAAA"}
@@ -17,6 +17,8 @@ tw completion install # Install shell completions
17
17
  tw update # Update CLI to latest version
18
18
  \`\`\`
19
19
 
20
+ Stored auth uses the system credential manager when available. If secure storage is unavailable, \`tw\` warns and falls back to \`~/.config/twist-cli/config.json\`. \`TWIST_API_TOKEN\` always takes priority over the stored token, and legacy plaintext config tokens are migrated automatically when secure storage is available.
21
+
20
22
  ## View by URL
21
23
 
22
24
  \`\`\`bash
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/lib/skills/content.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CAAA;AAErC,MAAM,CAAC,MAAM,iBAAiB,GAAG,4CAA4C,CAAA;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqM5B,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG;QAC1B,UAAU;eACH,iBAAiB;;;EAG9B,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/lib/skills/content.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,WAAW,CAAA;AAErC,MAAM,CAAC,MAAM,iBAAiB,GAAG,4CAA4C,CAAA;AAE7E,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuM5B,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG;QAC1B,UAAU;eACH,iBAAiB;;;EAG9B,aAAa,EAAE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/twist-cli",
3
- "version": "2.9.1",
3
+ "version": "2.10.0",
4
4
  "description": "TypeScript CLI for Twist",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -70,5 +70,8 @@
70
70
  "semantic-release": "25.0.3",
71
71
  "typescript": "5.9.3",
72
72
  "vitest": "4.0.18"
73
+ },
74
+ "optionalDependencies": {
75
+ "@napi-rs/keyring": "1.2.0"
73
76
  }
74
77
  }