@glassly/cli 2.0.3-dev.0 → 2.0.3-dev.1

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
@@ -62,5 +62,5 @@ The CLI talks to the production Glassly backend and Developer Console by
62
62
  default; nothing needs configuring. To point it at another deployment, set
63
63
  `CLOUD_CORE_URL`, `CLOUD_CONSOLE_URL`, and that backend's public WorkOS
64
64
  AuthKit client id in `WORKOS_CLIENT_ID`. Logins and the bundle signing key are
65
- stored per backend URL in `~/.glassly/cli-v2/`, readable only by your user;
65
+ stored per backend URL in `~/.glassly/cli/`, readable only by your user;
66
66
  `glassly logout` removes the login.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glassly/cli",
3
- "version": "2.0.3-dev.0",
3
+ "version": "2.0.3-dev.1",
4
4
  "description": "The glassly command: develop, pack, release, and publish Glassly miniapps. Bun-only.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,12 +2,9 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- const SERVICE = "glassly-cli-v2";
6
- const LEGACY_NAME = "credentials";
7
- const CONFIG_DIR = join(homedir(), ".glassly");
8
- const CREDS_DIR = join(CONFIG_DIR, "cli-v2");
9
- const LEGACY_CREDS_FILE = join(CREDS_DIR, "credentials.json");
10
- const SIGNING_KEY_SERVICE = "glassly-cli-v2-signing";
5
+ // Logins and the bundle signing key live as 0600 files under ~/.glassly/cli,
6
+ // one per backend URL, the way gh and npm keep theirs.
7
+ const CREDS_DIR = join(homedir(), ".glassly", "cli");
11
8
 
12
9
  export type CliJwk = Record<string, unknown> & {
13
10
  kty?: string;
@@ -36,142 +33,53 @@ export interface CliSigningKey {
36
33
  createdAt: string;
37
34
  }
38
35
 
39
- // Credentials live in a 0600 file under ~/.glassly/cli-v2, like gh and npm
40
- // keep theirs. The OS keychain (Bun.secrets) was the primary store before,
41
- // but on some machines a write there succeeds and the next process reads
42
- // nothing back, which showed up as "login works, whoami says sign in". The
43
- // keychain is still read so sessions stored there keep working.
44
- export async function saveCredentials(credentials: CliCredentials): Promise<"keychain" | "file"> {
45
- const payload = JSON.stringify(credentials);
46
- mkdirSync(CREDS_DIR, { recursive: true });
47
- writeFileSync(credentialsFile(credentials.coreUrl), `${payload}\n`, { mode: 0o600 });
36
+ export async function saveCredentials(credentials: CliCredentials): Promise<"file"> {
37
+ writeSecret(credentialsFile(credentials.coreUrl), credentials);
48
38
  return "file";
49
39
  }
50
40
 
51
- export async function loadCredentials(coreUrl?: string): Promise<CliCredentials | null> {
52
- const targetCoreUrl = coreUrl ? normalizeCoreUrl(coreUrl) : undefined;
41
+ export async function loadCredentials(coreUrl: string): Promise<CliCredentials | null> {
53
42
  if (process.env.CLI_TOKEN) {
54
43
  return {
55
44
  token: process.env.CLI_TOKEN,
56
45
  workosUserId: process.env.CLI_WORKOS_USER_ID || "unknown",
57
46
  email: process.env.CLI_EMAIL || "unknown",
58
47
  organizationId: process.env.CLI_ORGANIZATION_ID,
59
- coreUrl: targetCoreUrl || process.env.CLOUD_CORE_URL || "http://localhost:3000",
48
+ coreUrl: normalizeCoreUrl(coreUrl),
60
49
  storedAt: new Date().toISOString(),
61
50
  };
62
51
  }
63
-
64
- const scoped = targetCoreUrl ? await loadScopedCredentials(targetCoreUrl) : null;
65
- if (scoped) return scoped;
66
-
67
- try {
68
- if (typeof Bun !== "undefined" && Bun.secrets) {
69
- const value = await Bun.secrets.get({
70
- service: SERVICE,
71
- name: LEGACY_NAME,
72
- });
73
- if (value) {
74
- const legacy = JSON.parse(value) as CliCredentials;
75
- if (!targetCoreUrl || normalizeCoreUrl(legacy.coreUrl) === targetCoreUrl) return legacy;
76
- }
77
- }
78
- } catch {
79
- // Fall through.
80
- }
81
-
82
- try {
83
- if (existsSync(LEGACY_CREDS_FILE)) {
84
- const legacy = JSON.parse(readFileSync(LEGACY_CREDS_FILE, "utf8")) as CliCredentials;
85
- if (!targetCoreUrl || normalizeCoreUrl(legacy.coreUrl) === targetCoreUrl) return legacy;
86
- }
87
- } catch {
88
- // Treat corrupt credentials as logged out.
89
- }
90
-
91
- return null;
52
+ return readSecret<CliCredentials>(credentialsFile(coreUrl));
92
53
  }
93
54
 
94
- export async function clearCredentials(coreUrl?: string): Promise<void> {
95
- const targetCoreUrl = coreUrl ? normalizeCoreUrl(coreUrl) : undefined;
96
-
97
- try {
98
- if (typeof Bun !== "undefined" && Bun.secrets) {
99
- await Bun.secrets.set({
100
- service: SERVICE,
101
- name: targetCoreUrl ? credentialName(targetCoreUrl) : LEGACY_NAME,
102
- value: "",
103
- });
104
- }
105
- } catch {
106
- // Ignore keychain failures; remove file fallback below.
107
- }
108
-
109
- const path = targetCoreUrl ? credentialsFile(targetCoreUrl) : LEGACY_CREDS_FILE;
110
- if (existsSync(path)) {
111
- rmSync(path);
112
- }
55
+ export async function clearCredentials(coreUrl: string): Promise<void> {
56
+ const path = credentialsFile(coreUrl);
57
+ if (existsSync(path)) rmSync(path);
113
58
  }
114
59
 
115
- export async function saveSigningKey(key: CliSigningKey): Promise<"keychain" | "file"> {
116
- const payload = JSON.stringify(key);
117
- mkdirSync(CREDS_DIR, { recursive: true });
118
- writeFileSync(signingKeyFile(key.coreUrl), `${payload}\n`, { mode: 0o600 });
60
+ export async function saveSigningKey(key: CliSigningKey): Promise<"file"> {
61
+ writeSecret(signingKeyFile(key.coreUrl), key);
119
62
  return "file";
120
63
  }
121
64
 
122
65
  export async function loadSigningKey(coreUrl: string): Promise<CliSigningKey | null> {
123
- const targetCoreUrl = normalizeCoreUrl(coreUrl);
124
- try {
125
- const path = signingKeyFile(targetCoreUrl);
126
- if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8")) as CliSigningKey;
127
- } catch {
128
- // Treat corrupt key storage as missing.
129
- }
130
-
131
- // Keys created by earlier versions sit in the OS keychain.
132
- try {
133
- if (typeof Bun !== "undefined" && Bun.secrets) {
134
- const value = await Bun.secrets.get({
135
- service: SIGNING_KEY_SERVICE,
136
- name: credentialName(targetCoreUrl),
137
- });
138
- if (value) return JSON.parse(value) as CliSigningKey;
139
- }
140
- } catch {
141
- // Fall through.
142
- }
143
-
144
- return null;
66
+ return readSecret<CliSigningKey>(signingKeyFile(coreUrl));
145
67
  }
146
68
 
147
- async function loadScopedCredentials(coreUrl: string): Promise<CliCredentials | null> {
148
- try {
149
- const path = credentialsFile(coreUrl);
150
- if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8")) as CliCredentials;
151
- } catch {
152
- // Treat corrupt credentials as logged out.
153
- }
69
+ function writeSecret(path: string, value: unknown): void {
70
+ mkdirSync(CREDS_DIR, { recursive: true, mode: 0o700 });
71
+ writeFileSync(path, `${JSON.stringify(value)}\n`, { mode: 0o600 });
72
+ }
154
73
 
155
- // Sessions stored by earlier versions sit in the OS keychain.
74
+ function readSecret<T>(path: string): T | null {
156
75
  try {
157
- if (typeof Bun !== "undefined" && Bun.secrets) {
158
- const value = await Bun.secrets.get({
159
- service: SERVICE,
160
- name: credentialName(coreUrl),
161
- });
162
- if (value) return JSON.parse(value) as CliCredentials;
163
- }
76
+ if (existsSync(path)) return JSON.parse(readFileSync(path, "utf8")) as T;
164
77
  } catch {
165
- // Fall through.
78
+ // A corrupt file reads as logged out; the next login overwrites it.
166
79
  }
167
-
168
80
  return null;
169
81
  }
170
82
 
171
- function credentialName(coreUrl: string): string {
172
- return `credentials:${credentialKey(coreUrl)}`;
173
- }
174
-
175
83
  function credentialsFile(coreUrl: string): string {
176
84
  return join(CREDS_DIR, `credentials-${credentialKey(coreUrl)}.json`);
177
85
  }
package/src/index.ts CHANGED
@@ -122,7 +122,7 @@ program
122
122
  : expiresAtFromToken(token.access_token)
123
123
  ? new Date(expiresAtFromToken(token.access_token)! * 1000)
124
124
  : undefined;
125
- const storage = await saveCredentials({
125
+ await saveCredentials({
126
126
  token: token.access_token,
127
127
  refreshToken: token.refresh_token,
128
128
  workosUserId: token.user.id,
@@ -135,7 +135,7 @@ program
135
135
  });
136
136
  console.log(`Signed in as ${token.user.email}`);
137
137
  if (token.organization_id) console.log(`Organization: ${token.organization_id}`);
138
- console.log(`Credentials stored in ${storage === "keychain" ? "the OS keychain" : "~/.glassly/cli-v2 (readable only by you)"}`);
138
+ console.log("Credentials stored in ~/.glassly/cli (readable only by you)");
139
139
  return;
140
140
  }
141
141