@microtronics/studio-cli 1.2.0-alpha.2 → 1.2.0-alpha.3

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.
@@ -1,9 +1,19 @@
1
+ import { AtCommandHandler } from '@microtronics/device-master';
2
+ import { DdeModel } from '@microtronics/device-master';
3
+ import { DdeRecord } from '@microtronics/device-master';
1
4
  import { Document as Document_2 } from 'yaml';
5
+ import { EventEmitter } from 'node:events';
6
+ import { ITimeSource } from '@microtronics/device-master';
7
+ import { ITransport } from '@microtronics/device-master';
2
8
  import { LineCounter } from 'yaml';
9
+ import { LocalApiRequest } from '@microtronics/device-master';
10
+ import { LocalApiResponse } from '@microtronics/device-master';
11
+ import { MemoryStorage } from '@microtronics/device-master';
3
12
  import { ParsedNode } from 'yaml';
4
13
  import { Range as Range_2 } from 'yaml';
5
14
  import { SemVer } from 'semver';
6
15
  import { URI } from 'vscode-uri';
16
+ import { UtoMaster } from '@microtronics/device-master';
7
17
 
8
18
  export declare namespace APM {
9
19
  export enum TagPhase {
@@ -395,6 +405,18 @@ declare interface components {
395
405
  pathItems: never;
396
406
  }
397
407
 
408
+ /** One tag whose device stamp overtook the mirror and was therefore pulled. */
409
+ export declare interface ContainerChange {
410
+ tag: number;
411
+ /** DDE container name, when the tag maps to one. */
412
+ container: string | null;
413
+ alias?: string;
414
+ /** Device stamp40 the mirror now holds. */
415
+ stamp: number;
416
+ /** Decoded record, for tags that map to a DDE container. */
417
+ decoded?: DdeRecord | null;
418
+ }
419
+
398
420
  declare const createPawnCC: PawnModuleFactory;
399
421
 
400
422
  declare const createPawnDbg: PawnModuleFactory;
@@ -1776,6 +1798,8 @@ export declare namespace DefaultFiles {
1776
1798
  legacyAutoUplinkInc: string;
1777
1799
  reportTemplate: string;
1778
1800
  dloCfg: string;
1801
+ devMirror: string;
1802
+ devDeviceLog: string;
1779
1803
  };
1780
1804
  export const filePaths: {
1781
1805
  studio: {
@@ -1801,6 +1825,11 @@ export declare namespace DefaultFiles {
1801
1825
  };
1802
1826
  };
1803
1827
  libdeps: string;
1828
+ dev: {
1829
+ path: string;
1830
+ mirror: string;
1831
+ deviceLog: string;
1832
+ };
1804
1833
  };
1805
1834
  dist: {
1806
1835
  path: string;
@@ -2037,6 +2066,23 @@ export declare namespace Dependencies {
2037
2066
  export function unlinkLibraryApmParts(cwd: URI, fs: LocalFS, logger: Log.Logger, libraryName: string): Promise<void>;
2038
2067
  }
2039
2068
 
2069
+ /**
2070
+ * Start a `studio dev` session and its local HTTP server.
2071
+ *
2072
+ * Build → connect USB → flash → parse `dist/dde/dde.xml` → serve the myDatanet-compatible
2073
+ * `/api/**` surface plus `/dev/**` and `/events` on localhost.
2074
+ */
2075
+ export declare function dev(cwd: URI, fs: LocalFS, options: DevOptions): Promise<DevResult>;
2076
+
2077
+ /** Diagnostics of the last `build()`, as served by `/dev/status` and `/dev/build`. */
2078
+ export declare interface DevBuildState {
2079
+ running: boolean;
2080
+ ok: boolean | null;
2081
+ at: number | null;
2082
+ message?: string;
2083
+ diagnostics?: Package.MergedDiagnostics;
2084
+ }
2085
+
2040
2086
  /**
2041
2087
  * Development installation information
2042
2088
  */
@@ -2054,8 +2100,261 @@ export declare interface DevelopmentInstallInfo {
2054
2100
  registrationCodeNeeded?: boolean;
2055
2101
  }
2056
2102
 
2103
+ /** An error carrying the process exit code the CLI should terminate with. */
2104
+ export declare class DevError extends Error {
2105
+ readonly exitCode: DevExitCode;
2106
+ constructor(message: string, exitCode: DevExitCode);
2107
+ }
2108
+
2109
+ /**
2110
+ * Exit codes for `studio dev` / `studio device`. 0–3 mirror
2111
+ * {@link import('../myDatanetClient').RunExitCode}; 7–9 are dev-mode specific.
2112
+ */
2113
+ export declare enum DevExitCode {
2114
+ Success = 0,
2115
+ BuildFailed = 1,
2116
+ DeviceNotFound = 2,
2117
+ UsbFailed = 3,
2118
+ ModelMissing = 7,
2119
+ UnknownContainer = 8,
2120
+ WriteForbidden = 9
2121
+ }
2122
+
2057
2123
  export declare const DeviceProfiles: any;
2058
2124
 
2125
+ /**
2126
+ * The pair of transports (UTO data + AT debug) needed to talk to a connected device,
2127
+ * plus a way to release the underlying link.
2128
+ */
2129
+ declare interface DeviceTransports {
2130
+ uto: ITransport;
2131
+ /** Absent when the caller asked for the UTO interface only (`needDebug: false`). */
2132
+ debug?: ITransport;
2133
+ close(): Promise<void>;
2134
+ }
2135
+
2136
+ /** One ring-buffer record, as served by `GET /dev/logs` and the SSE `log` event. */
2137
+ export declare interface DevLogRecord {
2138
+ /** Monotonic sequence number; `GET /dev/logs?since=<seq>` returns everything after it. */
2139
+ seq: number;
2140
+ /** Wall-clock ms. */
2141
+ ts: number;
2142
+ source: DevLogSource;
2143
+ text: string;
2144
+ /** Base64 payload, only for records whose bytes could not be decoded to text. */
2145
+ raw?: string;
2146
+ }
2147
+
2148
+ /** Where a log record came from. */
2149
+ export declare type DevLogSource = 'rm2mlog' | 'at' | 'uto' | 'pov';
2150
+
2151
+ export declare interface DevOptions {
2152
+ serial: string;
2153
+ host?: string;
2154
+ port?: number;
2155
+ /** Allow a non-loopback `host` without a bearer token. */
2156
+ allowRemote?: boolean;
2157
+ /** Rebuild, flash and sync on source changes. */
2158
+ watch?: boolean;
2159
+ /** Build before connecting (default `true`). */
2160
+ build?: boolean;
2161
+ /** Flash `dist/dlo/main.amx` after connecting (default `true`). */
2162
+ flash?: boolean;
2163
+ pollIntervalMs?: number;
2164
+ /** How long a write waits for the device to confirm, in ms. */
2165
+ waitMs?: number;
2166
+ logFile?: string;
2167
+ /** Bearer token required by the server; absent ⇒ `Authorization` is ignored. */
2168
+ token?: string;
2169
+ part?: APM.Part[];
2170
+ debug?: boolean;
2171
+ env?: Globals.ENV;
2172
+ apiToken?: string | null;
2173
+ logger?: Log.Logger;
2174
+ /** Spawn `npm run dev -w pov/<name>` with `MYDATANET_HOST` pointing at this server. */
2175
+ pov?: string | boolean;
2176
+ /** Persist the mirror to `.studio/dev/mirror.json` (default `true`). */
2177
+ persistMirror?: boolean;
2178
+ /** Test/replay seams, forwarded to {@link DevSession}. */
2179
+ transportFactory?: TransportFactory;
2180
+ createDevice?: DevSessionOptions['createDevice'];
2181
+ }
2182
+
2183
+ export declare interface DevResult {
2184
+ url: string;
2185
+ port: number;
2186
+ session: DevSession;
2187
+ apmId: string;
2188
+ close(): Promise<void>;
2189
+ }
2190
+
2191
+ export declare interface DevServer {
2192
+ url: string;
2193
+ port: number;
2194
+ close(): Promise<void>;
2195
+ }
2196
+
2197
+ /**
2198
+ * One `studio dev` session: owns the USB link, the project's DDE model, the `IStorage`
2199
+ * mirror and the myDatanet-compatible API handler, and fans device activity out as events.
2200
+ *
2201
+ * Events: `log` ({@link DevLogRecord}), `state`, `sync`, `build`, `uto`,
2202
+ * `container-changed` ({@link ContainerChange}), `error`.
2203
+ */
2204
+ export declare class DevSession extends EventEmitter {
2205
+ readonly logStream: LogStream;
2206
+ private readonly opts;
2207
+ private readonly logger;
2208
+ private readonly timeSource;
2209
+ private device;
2210
+ private mirror;
2211
+ private model;
2212
+ private mapper;
2213
+ private apiHandler;
2214
+ private poller;
2215
+ private stateManager;
2216
+ private localData;
2217
+ private offlineSync;
2218
+ private buildState;
2219
+ private lastSyncAt;
2220
+ private firmwareVersion;
2221
+ private mirrorSaveTimer;
2222
+ private stopped;
2223
+ private readonly lock;
2224
+ constructor(options: DevSessionOptions);
2225
+ get serial(): string;
2226
+ get connected(): boolean;
2227
+ /**
2228
+ * Read by {@link StampPoller}: true while a flash, sync, write or AT command owns the
2229
+ * UTO link, so a poll tick cannot interleave with a multi-frame transfer.
2230
+ */
2231
+ get busy(): boolean;
2232
+ /** The parsed project DDE. */
2233
+ get ddeModel(): DdeModel;
2234
+ get storage(): MemoryStorage;
2235
+ /**
2236
+ * Build (optional) → load the model → connect USB → flash (optional) → start the stamp
2237
+ * poller. Rejects with a {@link DevError} whose `exitCode` the CLI exits with.
2238
+ */
2239
+ start(): Promise<void>;
2240
+ /** Read and parse `dist/dde/dde.xml`. Missing → {@link DevExitCode.ModelMissing}. */
2241
+ private loadModel;
2242
+ private connect;
2243
+ private startPoller;
2244
+ private tagLabel;
2245
+ private get utoMaster();
2246
+ private get atHandler();
2247
+ /** Assemble the {@link LocalApiContext} and bind the router to it. */
2248
+ private buildApiHandler;
2249
+ /**
2250
+ * Serve one `/api/**` request. A successful write additionally triggers an immediate
2251
+ * poll tick and waits (bounded by `waitMs`) until the device's own stamp for the
2252
+ * written tag has reached the pushed stamp — proof the device accepted the frame.
2253
+ * When it does not, `x-mt-mirror: stale` is added; `?async=1` skips the wait entirely.
2254
+ */
2255
+ handleApi(req: LocalApiRequest): Promise<LocalApiResponse>;
2256
+ /**
2257
+ * The `uto_data` tag a request writes, or `null` for a read / an `?async=1` write /
2258
+ * a path whose container cannot be resolved.
2259
+ */
2260
+ private writtenTag;
2261
+ /**
2262
+ * Poll until the device reports a stamp ≥ `pushedStamp` for `tag`, or the wait budget
2263
+ * runs out. Returns `false` on timeout.
2264
+ */
2265
+ private awaitDeviceStamp;
2266
+ /** Run the project build; records diagnostics in {@link DevBuildState}. */
2267
+ build(): Promise<DevBuildState>;
2268
+ /**
2269
+ * Upload an AMX to the device (defaults to `dist/dlo/main.amx`).
2270
+ *
2271
+ * `uploadToDevice` owns its own connection, and a USB device is single-owner, so the
2272
+ * session's link is released for the duration and reopened afterwards. The tag table
2273
+ * can change with a new app, so DEVINFO is re-read before the poller resumes.
2274
+ */
2275
+ flash(file?: string): Promise<UploadResult>;
2276
+ /** Reopen the USB link after a flash; a failure is logged and leaves `connected: false`. */
2277
+ private reconnect;
2278
+ /** Full `OfflineSyncManager` sync, or a targeted pull of the given tags. */
2279
+ sync(tags?: number[]): Promise<{
2280
+ tags: number[];
2281
+ at: number;
2282
+ }>;
2283
+ /** One AT command; queued behind a flash, which closes the AT handler's transport. */
2284
+ at(cmd: string): Promise<string>;
2285
+ /** Last known rm2m state; fetches one when nothing has been read yet. */
2286
+ state(): Promise<unknown>;
2287
+ /** Live local-data values. */
2288
+ localDataSnapshot(): unknown;
2289
+ /** Trigger a measurement and return its outcome plus the fresh values. */
2290
+ measure(): Promise<unknown>;
2291
+ status(): DevStatus;
2292
+ /** Force one poll pass (used by the watcher and by `POST /dev/sync`). */
2293
+ tick(): Promise<ContainerChange[]>;
2294
+ /** The mirror's current stamp40 for a tag, or 0. */
2295
+ mirrorStamp(tag: number): Promise<number>;
2296
+ /** Run `fn` as the sole owner of the UTO link (see {@link UtoLock}). */
2297
+ private withLink;
2298
+ private closeDevice;
2299
+ stop(): Promise<void>;
2300
+ }
2301
+
2302
+ export declare interface DevSessionOptions {
2303
+ cwd: URI;
2304
+ fs: LocalFS;
2305
+ /** 16-character hex device serial. */
2306
+ serial: string;
2307
+ /** Overrides how the USB transports are obtained (tests, later `--replay`). */
2308
+ transportFactory?: TransportFactory;
2309
+ /**
2310
+ * Test seam: supply the connected device (UtoMaster + AtCommandHandler) instead of
2311
+ * opening USB. The unit tests use it because `UtoMaster.init()` needs the real WASM
2312
+ * UTO wire protocol, which a byte-level mock cannot produce.
2313
+ */
2314
+ createDevice?: (serial: string) => Promise<OpenDevice>;
2315
+ /** Stamp-poll interval in ms; `0` disables polling. */
2316
+ pollIntervalMs?: number;
2317
+ /** Flash `dist/dlo/main.amx` to the device on start. */
2318
+ flash: boolean;
2319
+ /** Build the project on start. */
2320
+ build: boolean;
2321
+ part?: APM.Part[];
2322
+ logger: Log.Logger;
2323
+ timeSource?: ITimeSource;
2324
+ env?: Globals.ENV;
2325
+ apiToken?: string | null;
2326
+ /** How long a write waits for the device to confirm, in ms. `0` disables the wait. */
2327
+ waitMs?: number;
2328
+ /** JSONL log file for every {@link DevLogRecord}. */
2329
+ logFile?: string;
2330
+ /** Persist the mirror to `.studio/dev/mirror.json`. */
2331
+ persistMirror?: boolean;
2332
+ debug?: boolean;
2333
+ /** Test seam: replaces the AMX upload {@link flash} performs. */
2334
+ uploadToDevice?: typeof uploadToDevice;
2335
+ /** How long UTO work waits for the link while a flash owns it, in ms. */
2336
+ lockWaitMs?: number;
2337
+ }
2338
+
2339
+ /** `GET /dev/status` payload. */
2340
+ export declare interface DevStatus {
2341
+ serial: string;
2342
+ connected: boolean;
2343
+ apmId: string;
2344
+ firmwareVersion: string | null;
2345
+ model: {
2346
+ containers: Array<{
2347
+ name: string;
2348
+ alias?: string;
2349
+ kind: string;
2350
+ tag: number | null;
2351
+ }>;
2352
+ };
2353
+ lastSync: number | null;
2354
+ buildState: DevBuildState;
2355
+ poll: StampPollerStatus;
2356
+ }
2357
+
2059
2358
  export declare namespace DFILES {
2060
2359
  export enum Type {
2061
2360
  static = "static",
@@ -2624,6 +2923,53 @@ export declare namespace Log {
2624
2923
  }
2625
2924
  }
2626
2925
 
2926
+ /**
2927
+ * Ring buffer of device log records with SSE fan-out.
2928
+ *
2929
+ * Fed by two taps:
2930
+ * - `UtoMaster`'s `'utoFrame'` event, filtered to {@link UtoTag.LOG} (rm2mlog).
2931
+ * - `AtCommandHandler`'s `'data'` event, which the handler emits for every decoded
2932
+ * chunk of debug-channel RX (its documented "raw monitoring" hook) — this is where
2933
+ * a DLO's live `printf` output arrives.
2934
+ *
2935
+ * The LOG payload layout is not modelled: the record's leading bytes are decoded
2936
+ * best-effort (cp1252, then UTF-8) and cut at the first NUL. A payload that does not
2937
+ * decode to text is stored as base64 in {@link DevLogRecord.raw} with a hex preview as
2938
+ * its `text`, so nothing is silently dropped.
2939
+ */
2940
+ declare class LogStream extends EventEmitter {
2941
+ private readonly buffer;
2942
+ private readonly capacity;
2943
+ private readonly logFile;
2944
+ private readonly logger;
2945
+ private seq;
2946
+ private disposers;
2947
+ /** Serialises the JSONL appends so records cannot interleave mid-line. */
2948
+ private writeChain;
2949
+ /** `mkdir -p` of the log file's directory, done once instead of per record. */
2950
+ private logDirReady;
2951
+ constructor(opts?: {
2952
+ capacity?: number;
2953
+ logFile?: string;
2954
+ logger?: Log.Logger;
2955
+ });
2956
+ /** Append a record, evict the oldest when full, emit `record` and mirror to `--log-file`. */
2957
+ push(source: DevLogSource, text: string, raw?: Uint8Array): DevLogRecord;
2958
+ private appendToFile;
2959
+ /** Records after `since`, newest last, at most `limit` (`<= 0` ⇒ the default limit). */
2960
+ since(since?: number, limit?: number): DevLogRecord[];
2961
+ /** The highest sequence number handed out so far. */
2962
+ get lastSeq(): number;
2963
+ /** Tap rm2mlog frames off the UTO channel. */
2964
+ attachUtoMaster(utoMaster: UtoMaster): void;
2965
+ /** Tap the AT/debug channel RX, where a DLO's live `printf` output arrives. */
2966
+ attachAtHandler(atHandler: AtCommandHandler): void;
2967
+ /** Decode a device payload best-effort; keep the bytes when it is not text. */
2968
+ private pushDecoded;
2969
+ /** Detach the taps and await the pending JSONL appends, so no record is lost. */
2970
+ dispose(): Promise<void>;
2971
+ }
2972
+
2627
2973
  export declare namespace Manifest {
2628
2974
  /**
2629
2975
  * The source filename where the information is stored in
@@ -2928,6 +3274,20 @@ export declare interface MyDatanetUserInfo {
2928
3274
  customers?: MyDatanetCustomer[];
2929
3275
  }
2930
3276
 
3277
+ /**
3278
+ * A connected device: the UTO/AT primitives plus the raw transports, ready to use.
3279
+ * Call `close()` exactly once when done to dispose the AtCommandHandler/UtoMaster and
3280
+ * release the underlying transports.
3281
+ */
3282
+ declare interface OpenDevice {
3283
+ utoMaster: UtoMaster;
3284
+ /** Only present when the debug interface was claimed (`needDebug`, the default). */
3285
+ atHandler?: AtCommandHandler;
3286
+ transports: DeviceTransports;
3287
+ serial: string;
3288
+ close(): Promise<void>;
3289
+ }
3290
+
2931
3291
  /**
2932
3292
  * Packs the given directory into a deployable package using the specified configuration.
2933
3293
  *
@@ -5335,6 +5695,40 @@ export declare namespace Registry {
5335
5695
  } | {};
5336
5696
  }
5337
5697
 
5698
+ /** `GET /dev/status`'s `poll` section. */
5699
+ export declare interface StampPollerStatus {
5700
+ intervalMs: number;
5701
+ lastTick: number | null;
5702
+ lastChange: number | null;
5703
+ errors: number;
5704
+ paused: boolean;
5705
+ }
5706
+
5707
+ /**
5708
+ * Opens the transports for a device given its serial number. The default (WebUSB) factory
5709
+ * is used by {@link openDevice} unless a test/consumer supplies its own.
5710
+ */
5711
+ declare type TransportFactory = (serial: string, needDebug?: boolean) => Promise<DeviceTransports>;
5712
+
5713
+ /**
5714
+ * Upload result interface
5715
+ */
5716
+ declare interface UploadResult {
5717
+ success: boolean;
5718
+ message: string;
5719
+ }
5720
+
5721
+ /**
5722
+ * Upload an AMX binary file to a USB device
5723
+ * @param serialNumber - The serial number of the target device
5724
+ * @param fileUri - The URI to the AMX file
5725
+ * @param fs - The filesystem interface for file operations
5726
+ * @param logger - The logger instance for output
5727
+ * @param debugLog - Enable debug logging
5728
+ * @returns Promise resolving to an UploadResult
5729
+ */
5730
+ declare function uploadToDevice(serialNumber: string, fileUri: URI, fs: LocalFS, logger: Log.Logger, debugLog?: boolean): Promise<UploadResult>;
5731
+
5338
5732
  declare function uriToMemFsPath(path: URI): string;
5339
5733
 
5340
5734
  declare interface XMLField {