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

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,266 @@ 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
+ /**
2277
+ * Reopen the USB link after a flash. The device is rebooting — and some devices don't even
2278
+ * drop the USB connection — so a failure here is expected and retried for a while rather
2279
+ * than reported immediately; a final failure is logged and leaves `connected: false`.
2280
+ */
2281
+ private reconnect;
2282
+ private reportReconnectFailure;
2283
+ /** Full `OfflineSyncManager` sync, or a targeted pull of the given tags. */
2284
+ sync(tags?: number[]): Promise<{
2285
+ tags: number[];
2286
+ at: number;
2287
+ }>;
2288
+ /** One AT command; queued behind a flash, which closes the AT handler's transport. */
2289
+ at(cmd: string): Promise<string>;
2290
+ /** Last known rm2m state; fetches one when nothing has been read yet. */
2291
+ state(): Promise<unknown>;
2292
+ /** Live local-data values. */
2293
+ localDataSnapshot(): unknown;
2294
+ /** Trigger a measurement and return its outcome plus the fresh values. */
2295
+ measure(): Promise<unknown>;
2296
+ status(): DevStatus;
2297
+ /** Force one poll pass (used by the watcher and by `POST /dev/sync`). */
2298
+ tick(): Promise<ContainerChange[]>;
2299
+ /** The mirror's current stamp40 for a tag, or 0. */
2300
+ mirrorStamp(tag: number): Promise<number>;
2301
+ /** Run `fn` as the sole owner of the UTO link (see {@link UtoLock}). */
2302
+ private withLink;
2303
+ private closeDevice;
2304
+ stop(): Promise<void>;
2305
+ }
2306
+
2307
+ export declare interface DevSessionOptions {
2308
+ cwd: URI;
2309
+ fs: LocalFS;
2310
+ /** 16-character hex device serial. */
2311
+ serial: string;
2312
+ /** Overrides how the USB transports are obtained (tests, later `--replay`). */
2313
+ transportFactory?: TransportFactory;
2314
+ /**
2315
+ * Test seam: supply the connected device (UtoMaster + AtCommandHandler) instead of
2316
+ * opening USB. The unit tests use it because `UtoMaster.init()` needs the real WASM
2317
+ * UTO wire protocol, which a byte-level mock cannot produce.
2318
+ */
2319
+ createDevice?: (serial: string) => Promise<OpenDevice>;
2320
+ /** Stamp-poll interval in ms; `0` disables polling. */
2321
+ pollIntervalMs?: number;
2322
+ /** Flash `dist/dlo/main.amx` to the device on start. */
2323
+ flash: boolean;
2324
+ /** Build the project on start. */
2325
+ build: boolean;
2326
+ part?: APM.Part[];
2327
+ logger: Log.Logger;
2328
+ timeSource?: ITimeSource;
2329
+ env?: Globals.ENV;
2330
+ apiToken?: string | null;
2331
+ /** How long a write waits for the device to confirm, in ms. `0` disables the wait. */
2332
+ waitMs?: number;
2333
+ /** JSONL log file for every {@link DevLogRecord}. */
2334
+ logFile?: string;
2335
+ /** Persist the mirror to `.studio/dev/mirror.json`. */
2336
+ persistMirror?: boolean;
2337
+ debug?: boolean;
2338
+ /** Test seam: replaces the AMX upload {@link flash} performs. */
2339
+ uploadToDevice?: typeof uploadToDevice;
2340
+ /** How long UTO work waits for the link while a flash owns it, in ms. */
2341
+ lockWaitMs?: number;
2342
+ }
2343
+
2344
+ /** `GET /dev/status` payload. */
2345
+ export declare interface DevStatus {
2346
+ serial: string;
2347
+ connected: boolean;
2348
+ apmId: string;
2349
+ firmwareVersion: string | null;
2350
+ model: {
2351
+ containers: Array<{
2352
+ name: string;
2353
+ alias?: string;
2354
+ kind: string;
2355
+ tag: number | null;
2356
+ }>;
2357
+ };
2358
+ lastSync: number | null;
2359
+ buildState: DevBuildState;
2360
+ poll: StampPollerStatus;
2361
+ }
2362
+
2059
2363
  export declare namespace DFILES {
2060
2364
  export enum Type {
2061
2365
  static = "static",
@@ -2598,7 +2902,8 @@ export declare namespace Log {
2598
2902
  done = 1,
2599
2903
  info = 2,
2600
2904
  warning = 3,
2601
- error = 4
2905
+ error = 4,
2906
+ debug = 5
2602
2907
  }
2603
2908
  export const LOG_PREFIX: {
2604
2909
  1: string;
@@ -2606,17 +2911,25 @@ export declare namespace Log {
2606
2911
  2: string;
2607
2912
  3: string;
2608
2913
  4: string;
2914
+ 5: string;
2609
2915
  };
2610
2916
  export class Logger {
2611
2917
  private _logHandler;
2612
2918
  private _silent;
2919
+ private _debugEnabled;
2613
2920
  constructor(logOutput?: {
2614
2921
  log: (...args: any[]) => void;
2615
2922
  });
2616
2923
  _log(type: LogMessageType, msg: any, ...args: any[]): void;
2617
2924
  set silent(value: boolean);
2925
+ /** Redirect where log lines go (e.g. stderr while stdout carries machine-readable output). */
2926
+ set output(logOutput: {
2927
+ log: (...args: any[]) => void;
2928
+ });
2618
2929
  log(msg: any, ...args: any[]): void;
2930
+ /** Only prints when {@link debugEnabled} is set (the `--debug` flag). */
2619
2931
  debug(msg: any, ...args: any[]): void;
2932
+ set debugEnabled(value: boolean);
2620
2933
  error(msg: any, ...args: any[]): void;
2621
2934
  info(msg: any, ...args: any[]): void;
2622
2935
  warn(msg: any, ...args: any[]): void;
@@ -2624,6 +2937,58 @@ export declare namespace Log {
2624
2937
  }
2625
2938
  }
2626
2939
 
2940
+ /**
2941
+ * Ring buffer of device log records with SSE fan-out.
2942
+ *
2943
+ * Fed by two taps:
2944
+ * - `UtoMaster`'s `'utoFrame'` event, filtered to {@link UtoTag.LOG} (rm2mlog).
2945
+ * - `AtCommandHandler`'s `'data'` event, which the handler emits for every decoded
2946
+ * chunk of debug-channel RX (its documented "raw monitoring" hook) — this is where
2947
+ * a DLO's live `printf` output arrives.
2948
+ *
2949
+ * The LOG payload layout is not modelled: the record's leading bytes are decoded
2950
+ * best-effort (cp1252, then UTF-8) and cut at the first NUL. A payload that does not
2951
+ * decode to text is stored as base64 in {@link DevLogRecord.raw} with a hex preview as
2952
+ * its `text`, so nothing is silently dropped.
2953
+ */
2954
+ declare class LogStream extends EventEmitter {
2955
+ private readonly buffer;
2956
+ private readonly capacity;
2957
+ private readonly logFile;
2958
+ private readonly logger;
2959
+ private seq;
2960
+ private disposers;
2961
+ /** Serialises the JSONL appends so records cannot interleave mid-line. */
2962
+ private writeChain;
2963
+ /** `mkdir -p` of the log file's directory, done once instead of per record. */
2964
+ private logDirReady;
2965
+ constructor(opts?: {
2966
+ capacity?: number;
2967
+ logFile?: string;
2968
+ logger?: Log.Logger;
2969
+ });
2970
+ /** Append a record, evict the oldest when full, emit `record` and mirror to `--log-file`. */
2971
+ push(source: DevLogSource, text: string, raw?: Uint8Array): DevLogRecord;
2972
+ private appendToFile;
2973
+ /** Records after `since`, newest last, at most `limit` (`<= 0` ⇒ the default limit). */
2974
+ since(since?: number, limit?: number): DevLogRecord[];
2975
+ /** The highest sequence number handed out so far. */
2976
+ get lastSeq(): number;
2977
+ /** Tap rm2mlog frames off the UTO channel. */
2978
+ attachUtoMaster(utoMaster: UtoMaster): void;
2979
+ /**
2980
+ * Tap the AT/debug channel RX, where a DLO's live `printf` output arrives.
2981
+ *
2982
+ * A `DebugMaster` parses the raw byte stream (console frames, the `!`/`?`/`~`/`$`
2983
+ * level prefixes and the DLO file/line tags) instead of dumping unformatted chunks.
2984
+ */
2985
+ attachAtHandler(atHandler: AtCommandHandler): void;
2986
+ /** Decode a device payload best-effort; keep the bytes when it is not text. */
2987
+ private pushDecoded;
2988
+ /** Detach the taps and await the pending JSONL appends, so no record is lost. */
2989
+ dispose(): Promise<void>;
2990
+ }
2991
+
2627
2992
  export declare namespace Manifest {
2628
2993
  /**
2629
2994
  * The source filename where the information is stored in
@@ -2928,6 +3293,20 @@ export declare interface MyDatanetUserInfo {
2928
3293
  customers?: MyDatanetCustomer[];
2929
3294
  }
2930
3295
 
3296
+ /**
3297
+ * A connected device: the UTO/AT primitives plus the raw transports, ready to use.
3298
+ * Call `close()` exactly once when done to dispose the AtCommandHandler/UtoMaster and
3299
+ * release the underlying transports.
3300
+ */
3301
+ declare interface OpenDevice {
3302
+ utoMaster: UtoMaster;
3303
+ /** Only present when the debug interface was claimed (`needDebug`, the default). */
3304
+ atHandler?: AtCommandHandler;
3305
+ transports: DeviceTransports;
3306
+ serial: string;
3307
+ close(): Promise<void>;
3308
+ }
3309
+
2931
3310
  /**
2932
3311
  * Packs the given directory into a deployable package using the specified configuration.
2933
3312
  *
@@ -5335,6 +5714,42 @@ export declare namespace Registry {
5335
5714
  } | {};
5336
5715
  }
5337
5716
 
5717
+ /** `GET /dev/status`'s `poll` section. */
5718
+ export declare interface StampPollerStatus {
5719
+ intervalMs: number;
5720
+ lastTick: number | null;
5721
+ lastChange: number | null;
5722
+ errors: number;
5723
+ paused: boolean;
5724
+ }
5725
+
5726
+ /**
5727
+ * Opens the transports for a device given its serial number. The default (WebUSB) factory
5728
+ * is used by {@link openDevice} unless a test/consumer supplies its own.
5729
+ */
5730
+ declare type TransportFactory = (serial: string, needDebug?: boolean) => Promise<DeviceTransports>;
5731
+
5732
+ /**
5733
+ * Upload result interface
5734
+ */
5735
+ declare interface UploadResult {
5736
+ success: boolean;
5737
+ message: string;
5738
+ }
5739
+
5740
+ /**
5741
+ * Upload an AMX binary file to a USB device
5742
+ * @param serialNumber - The serial number of the target device
5743
+ * @param fileUri - The URI to the AMX file
5744
+ * @param fs - The filesystem interface for file operations
5745
+ * @param logger - The logger instance for output
5746
+ * @param debugLog - Enable debug logging
5747
+ * @param retryMs - Total ms budget to retry a transient device-open failure (see
5748
+ * {@link isTransientUsbOpenError}); `0` (the default) attempts once.
5749
+ * @returns Promise resolving to an UploadResult
5750
+ */
5751
+ declare function uploadToDevice(serialNumber: string, fileUri: URI, fs: LocalFS, logger: Log.Logger, debugLog?: boolean, retryMs?: number): Promise<UploadResult>;
5752
+
5338
5753
  declare function uriToMemFsPath(path: URI): string;
5339
5754
 
5340
5755
  declare interface XMLField {