@almadar/workspace 0.2.5 → 0.3.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.
@@ -20,4 +20,6 @@ export declare class LocalBackend implements WorkspaceBackend {
20
20
  isDirectory: boolean;
21
21
  }>;
22
22
  rename(srcAbs: string, dstAbs: string): Promise<void>;
23
+ chmod(absPath: string, mode: number): Promise<void>;
24
+ watch(absPath: string, onChange: (kind: 'change' | 'rename') => void): () => void;
23
25
  }
@@ -21,6 +21,8 @@ export declare class MemoryBackend implements WorkspaceBackend {
21
21
  isDirectory: boolean;
22
22
  }>;
23
23
  rename(srcAbs: string, dstAbs: string): Promise<void>;
24
+ chmod(): Promise<void>;
25
+ watch(): () => void;
24
26
  getAll(): Map<string, string>;
25
27
  clear(): void;
26
28
  }
@@ -35,6 +35,17 @@ export interface WorkspaceBackend {
35
35
  * exists; backend ensures `dstAbs`'s parent dir exists before move.
36
36
  */
37
37
  rename(srcAbs: string, dstAbs: string): Promise<void>;
38
+ /** Set file mode bits (e.g. `0o600` for a secrets file). No-op on backends without a real fs. */
39
+ chmod(absPath: string, mode: number): Promise<void>;
40
+ /**
41
+ * Watch an absolute path (file or directory) for EXTERNAL changes — edits
42
+ * made outside this service (a user editing a `.orb` in another editor, a
43
+ * remote sync landing). Returns a closer that stops watching. The `'local'`
44
+ * backend implements this with `fs.watch`; backends without a real fs
45
+ * (memory) may no-op. A future remote/github backend implements it its own
46
+ * way (poll/webhook) — consumers never see the difference.
47
+ */
48
+ watch(absPath: string, onChange: (kind: 'change' | 'rename') => void): () => void;
38
49
  }
39
50
  /**
40
51
  * The persisted marker that pins an `appId` to a workspace dir on disk.
package/dist/service.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * @packageDocumentation
8
8
  */
9
9
  import type { JsonObject, JsonValue } from '@almadar/core';
10
- import type { FileTreeNode, GitHubConfig, GitStatusInfo, WorkspaceObserver, WorkspaceService } from './types.js';
10
+ import type { FileTreeNode, GitHubConfig, GitStatusInfo, WorkspaceObserver, WorkspaceService, WorkspaceWatchEvent } from './types.js';
11
11
  import type { WorkspaceBackend } from './internal/types.js';
12
12
  import { SinkManager } from './internal/sink-manager.js';
13
13
  import { GitClient } from './internal/git-client.js';
@@ -85,6 +85,7 @@ export declare class WorkspaceServiceImpl implements WorkspaceService {
85
85
  pullIfLinked(): Promise<boolean>;
86
86
  gitStatus(): Promise<GitStatusInfo>;
87
87
  subscribe(observer: WorkspaceObserver): () => void;
88
+ watch(relPath: string, onChange: (event: WorkspaceWatchEvent) => void): () => void;
88
89
  dispose(): Promise<void>;
89
90
  }
90
91
  export {};
package/dist/types.d.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * @packageDocumentation
8
8
  */
9
9
  import type { JsonObject, JsonValue } from '@almadar/core';
10
+ import type { ProviderConfig } from '@almadar/llm';
10
11
  import type { EmbedderPort, WorkspaceIndex } from './workspace-index/types.js';
11
12
  /**
12
13
  * The only extension point. Consumers register one observer via
@@ -97,6 +98,18 @@ export type WorkspaceWriteEvent = {
97
98
  kind: 'workspace-index-manifest';
98
99
  content: JsonObject;
99
100
  };
101
+ /**
102
+ * Emitted by {@link WorkspaceService.watch} when storage changes underneath the
103
+ * service — an edit made OUTSIDE this service (another editor, a remote sync).
104
+ * Distinct from {@link WorkspaceObserver}, which fires on writes the service
105
+ * itself performs.
106
+ */
107
+ export interface WorkspaceWatchEvent {
108
+ /** Workspace-relative POSIX path that changed. */
109
+ relPath: string;
110
+ /** `'change'` = content edited; `'rename'` = created / deleted / moved. */
111
+ kind: 'change' | 'rename';
112
+ }
100
113
  /**
101
114
  * Caller-supplied backend used when the local cache misses but the
102
115
  * workspace state lives somewhere addressable (Firestore, S3, etc.).
@@ -152,6 +165,15 @@ export interface OpenWorkspaceOptions {
152
165
  appId?: string;
153
166
  /** Adopt an existing absolute workDir as-is. */
154
167
  adopt?: string;
168
+ /**
169
+ * Bare adopt — use the adopted dir WITHOUT scaffolding it: no `.almadar/`
170
+ * skeleton, no mint templates, no app-marker, no index warm. For opening a
171
+ * loose external file in place (desktop file-centric editing): read / write /
172
+ * watch the file via the sandboxed relPath (its basename), leaving zero litter
173
+ * in the user's directory. Only meaningful together with `adopt`; ignored
174
+ * otherwise.
175
+ */
176
+ bare?: boolean;
155
177
  /** Clone-on-resume / push-on-dispose configuration. */
156
178
  github?: GitHubConfig;
157
179
  /** Fallback fetcher used when the local cache misses. */
@@ -233,6 +255,83 @@ export interface WorkspaceService {
233
255
  pullIfLinked(): Promise<boolean>;
234
256
  gitStatus(): Promise<GitStatusInfo>;
235
257
  subscribe(observer: WorkspaceObserver): () => void;
258
+ /**
259
+ * Watch a workspace-relative path (file or directory) for EXTERNAL changes —
260
+ * edits made outside this service, e.g. a user editing a `.orb` in another
261
+ * editor (desktop hot-reload). Unlike `subscribe`, which is write-side only.
262
+ * Backend-agnostic: `'local'` uses `fs.watch`; a remote/github backend its own
263
+ * mechanism. Returns an unsubscribe function. Rejects `..` / absolute paths.
264
+ */
265
+ watch(relPath: string, onChange: (event: WorkspaceWatchEvent) => void): () => void;
236
266
  readonly index: WorkspaceIndex;
237
267
  dispose(): Promise<void>;
238
268
  }
269
+ /**
270
+ * Non-secret account config, persisted to `~/.almadar/config.json`. Safe to
271
+ * inspect/sync. Secrets live in `credentials.json` (see {@link ProviderCredential}).
272
+ */
273
+ export interface AccountConfig {
274
+ schemaVersion: 1;
275
+ /** ISO timestamp; absence signals the first-run setup hasn't completed. */
276
+ setupCompletedAt?: string;
277
+ defaultProvider: string;
278
+ defaultModel: string;
279
+ autonomy: 'full' | 'balanced' | 'cautious';
280
+ budget?: number;
281
+ /** Per-provider non-secret overrides, keyed by provider name. */
282
+ providers?: Record<string, {
283
+ baseUrl?: string;
284
+ model?: string;
285
+ }>;
286
+ /** Service endpoints (override the built-in defaults). */
287
+ services?: {
288
+ authUrl?: string;
289
+ builderUrl?: string;
290
+ masarUrl?: string;
291
+ };
292
+ /** Where the active config came from. */
293
+ source: 'local' | 'studio';
294
+ }
295
+ /** A single provider's secret, persisted to `~/.almadar/credentials.json` (chmod 600). */
296
+ export interface ProviderCredential {
297
+ apiKey: string;
298
+ }
299
+ /** Studio-synced identity overlay, persisted to `~/.almadar/account.json`. Empty until login. */
300
+ export interface AccountIdentity {
301
+ uid: string;
302
+ email?: string;
303
+ tier?: string;
304
+ teamId?: string | null;
305
+ syncedAt: string;
306
+ }
307
+ export interface OpenAccountOptions {
308
+ /** Home directory root. Defaults to `os.homedir()`. */
309
+ home?: string;
310
+ /** Storage backend. Defaults to `'local'` (real filesystem). */
311
+ backend?: 'local' | 'memory';
312
+ }
313
+ /**
314
+ * Account-level store rooted at `~/.almadar/`. Home-rooted and NOT sandboxed
315
+ * (unlike {@link WorkspaceService}); owns config + credentials + the Studio
316
+ * identity overlay. Construct via `openAccount`.
317
+ */
318
+ export interface AccountService {
319
+ /** Absolute `~/.almadar` path. */
320
+ readonly root: string;
321
+ getConfig(): Promise<AccountConfig>;
322
+ setConfig(patch: Partial<AccountConfig>): Promise<void>;
323
+ getCredential(provider: string): Promise<ProviderCredential | null>;
324
+ setCredential(provider: string, cred: ProviderCredential): Promise<void>;
325
+ listCredentialedProviders(): Promise<string[]>;
326
+ readAccount(): Promise<AccountIdentity | null>;
327
+ writeAccount(identity: AccountIdentity): Promise<void>;
328
+ /**
329
+ * Resolve the explicit {@link ProviderConfig} for `provider` (stored apiKey +
330
+ * config baseUrl/model overrides), ready to pass into `@almadar/llm` / rabit.
331
+ * Returns null when no credential is stored for `provider`.
332
+ */
333
+ resolveProviderConfig(provider: string): Promise<ProviderConfig | null>;
334
+ /** True once setup completed AND at least one provider has a stored credential. */
335
+ isConfigured(): Promise<boolean>;
336
+ dispose(): Promise<void>;
337
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/workspace",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Storage-agnostic workspace primitives shared by Almadar consumers. One service, six exports, hidden paths, single observer. See docs/Almadar_Workspace.md.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",